diff --git a/src/block/Anvil.php b/src/block/Anvil.php index 4b4afef6153..1fb71025ccb 100644 --- a/src/block/Anvil.php +++ b/src/block/Anvil.php @@ -23,7 +23,7 @@ namespace pocketmine\block; -use pocketmine\block\inventory\AnvilInventory; +use pocketmine\block\inventory\window\AnvilInventoryWindow; use pocketmine\block\utils\Fallable; use pocketmine\block\utils\FallableTrait; use pocketmine\block\utils\HorizontalFacingTrait; @@ -83,7 +83,7 @@ public function getSupportType(int $facing) : SupportType{ 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 0f0499ab93b..1b64e814c58 100644 --- a/src/block/Barrel.php +++ b/src/block/Barrel.php @@ -23,7 +23,10 @@ namespace pocketmine\block; +use pocketmine\block\inventory\window\BlockInventoryWindow; use pocketmine\block\tile\Barrel as TileBarrel; +use pocketmine\block\utils\AnimatedContainer; +use pocketmine\block\utils\AnimatedContainerTrait; use pocketmine\block\utils\AnyFacingTrait; use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\item\Item; @@ -31,9 +34,14 @@ use pocketmine\math\Vector3; use pocketmine\player\Player; use pocketmine\world\BlockTransaction; +use pocketmine\world\Position; +use pocketmine\world\sound\BarrelCloseSound; +use pocketmine\world\sound\BarrelOpenSound; +use pocketmine\world\sound\Sound; use function abs; -class Barrel extends Opaque{ +class Barrel extends Opaque implements AnimatedContainer{ + use AnimatedContainerTrait; use AnyFacingTrait; protected bool $open = false; @@ -81,7 +89,7 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player return true; } - $player->setCurrentWindow($barrel->getInventory()); + $player->setCurrentWindow(new BlockInventoryWindow($player, $barrel->getInventory(), $this->position)); } } @@ -91,4 +99,20 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player public function getFuelTime() : int{ return 300; } + + protected function getContainerOpenSound() : Sound{ + return new BarrelOpenSound(); + } + + protected function getContainerCloseSound() : Sound{ + return new BarrelCloseSound(); + } + + protected function doContainerAnimation(Position $position, bool $isOpen) : void{ + $world = $position->getWorld(); + $block = $world->getBlock($position); + if($block instanceof Barrel){ + $world->setBlock($position, $block->setOpen($isOpen)); + } + } } diff --git a/src/block/BrewingStand.php b/src/block/BrewingStand.php index 03969316494..59e439b91e2 100644 --- a/src/block/BrewingStand.php +++ b/src/block/BrewingStand.php @@ -23,6 +23,7 @@ namespace pocketmine\block; +use pocketmine\block\inventory\window\BrewingStandInventoryWindow; use pocketmine\block\tile\BrewingStand as TileBrewingStand; use pocketmine\block\utils\BrewingStandSlot; use pocketmine\block\utils\SupportType; @@ -99,7 +100,7 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player 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 ce759ee87f7..d25f3f2cd73 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 @@ public function readStateFromWorld() : Block{ $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 67d950c5ac6..1c3e94096aa 100644 --- a/src/block/CartographyTable.php +++ b/src/block/CartographyTable.php @@ -23,7 +23,7 @@ namespace pocketmine\block; -use pocketmine\block\inventory\CartographyTableInventory; +use pocketmine\block\inventory\window\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 dca21576aa9..a688df0bd43 100644 --- a/src/block/Chest.php +++ b/src/block/Chest.php @@ -23,7 +23,12 @@ namespace pocketmine\block; +use pocketmine\block\inventory\DoubleChestInventory; +use pocketmine\block\inventory\window\BlockInventoryWindow; +use pocketmine\block\inventory\window\DoubleChestInventoryWindow; use pocketmine\block\tile\Chest as TileChest; +use pocketmine\block\utils\AnimatedContainer; +use pocketmine\block\utils\AnimatedContainerTrait; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; use pocketmine\block\utils\SupportType; use pocketmine\event\block\ChestPairEvent; @@ -31,9 +36,17 @@ use pocketmine\math\AxisAlignedBB; use pocketmine\math\Facing; use pocketmine\math\Vector3; +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\ChestCloseSound; +use pocketmine\world\sound\ChestOpenSound; +use pocketmine\world\sound\Sound; +use function count; -class Chest extends Transparent{ +class Chest extends Transparent implements AnimatedContainer{ + use AnimatedContainerTrait; use FacesOppositePlacingPlayerTrait; /** @@ -48,6 +61,25 @@ public function getSupportType(int $facing) : SupportType{ return SupportType::NONE; } + /** + * @phpstan-return array{bool, TileChest}|null + */ + private function locatePair(Position $position) : ?array{ + $world = $position->getWorld(); + $tile = $world->getTile($position); + if($tile instanceof TileChest){ + foreach([false, true] as $clockwise){ + $side = Facing::rotateY($this->facing, $clockwise); + $c = $position->getSide($side); + $pair = $world->getTile($c); + if($pair instanceof TileChest && $pair->isPaired() && $pair->getPair() === $tile){ + return [$clockwise, $pair]; + } + } + } + return null; + } + public function onPostPlace() : void{ $world = $this->position->getWorld(); $tile = $world->getTile($this->position); @@ -74,8 +106,8 @@ public function onPostPlace() : void{ 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 +117,25 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player return true; } - $player->setCurrentWindow($chest->getInventory()); + $window = null; + if($chest->isPaired()){ + $info = $this->locatePair($this->position); + if($info !== null){ + [$clockwise, $pair] = $info; + [$left, $right] = $clockwise ? [$pair, $chest] : [$chest, $pair]; + + $doubleInventory = $left->getDoubleInventory() ?? $right->getDoubleInventory(); + if($doubleInventory === null){ + $doubleInventory = new DoubleChestInventory($left->getInventory(), $right->getInventory()); + $left->setDoubleInventory($doubleInventory); + $right->setDoubleInventory($doubleInventory); + } + + $window = new DoubleChestInventoryWindow($player, $doubleInventory, $left->getPosition(), $right->getPosition()); + } + } + + $player->setCurrentWindow($window ?? new BlockInventoryWindow($player, $chest->getInventory(), $this->position)); } } @@ -95,4 +145,39 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player public function getFuelTime() : int{ return 300; } + + protected function getContainerViewerCount() : int{ + $tile = $this->position->getWorld()->getTile($this->position); + if($tile instanceof TileChest){ + $inventory = $tile->getDoubleInventory() ?? $tile->getInventory(); + return count($inventory->getViewers()); + } + return 0; + } + + protected function getContainerOpenSound() : Sound{ + return new ChestOpenSound(); + } + + protected function getContainerCloseSound() : Sound{ + return new ChestCloseSound(); + } + + protected function doContainerAnimation(Position $position, bool $isOpen) : void{ + //event ID is always 1 for a chest + //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)); + } + + protected function doContainerEffects(bool $isOpen) : void{ + $this->doContainerAnimation($this->position, $isOpen); + $this->playContainerSound($this->position, $isOpen); + + $pairInfo = $this->locatePair($this->position); + if($pairInfo !== null){ + [, $pair] = $pairInfo; + $this->doContainerAnimation($pair->getPosition(), $isOpen); + $this->playContainerSound($pair->getPosition(), $isOpen); + } + } } diff --git a/src/block/CraftingTable.php b/src/block/CraftingTable.php index dcd9edce2cd..2b73d221aeb 100644 --- a/src/block/CraftingTable.php +++ b/src/block/CraftingTable.php @@ -23,7 +23,7 @@ namespace pocketmine\block; -use pocketmine\block\inventory\CraftingTableInventory; +use pocketmine\block\inventory\window\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 6a6c936b220..46ecc070277 100644 --- a/src/block/EnchantingTable.php +++ b/src/block/EnchantingTable.php @@ -23,7 +23,7 @@ namespace pocketmine\block; -use pocketmine\block\inventory\EnchantInventory; +use pocketmine\block\inventory\window\EnchantingTableInventoryWindow; use pocketmine\block\utils\SupportType; use pocketmine\item\Item; use pocketmine\math\AxisAlignedBB; @@ -48,7 +48,7 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player 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 9004f7c79f9..c5901d3f4bb 100644 --- a/src/block/EnderChest.php +++ b/src/block/EnderChest.php @@ -23,17 +23,29 @@ namespace pocketmine\block; -use pocketmine\block\inventory\EnderChestInventory; +use pocketmine\block\inventory\window\BlockInventoryWindow; use pocketmine\block\tile\EnderChest as TileEnderChest; +use pocketmine\block\utils\AnimatedContainer; +use pocketmine\block\utils\AnimatedContainerTrait; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; use pocketmine\block\utils\SupportType; use pocketmine\item\Item; use pocketmine\math\AxisAlignedBB; use pocketmine\math\Facing; use pocketmine\math\Vector3; +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; -class EnderChest extends Transparent{ +class EnderChest extends Transparent implements AnimatedContainer{ + use AnimatedContainerTrait { + onContainerOpen as private traitOnContainerOpen; + onContainerClose as private traitOnContainerClose; + } use FacesOppositePlacingPlayerTrait; public function getLightLevel() : int{ @@ -56,8 +68,7 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player 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 BlockInventoryWindow($player, $player->getEnderInventory(), $this->position)); } } @@ -73,4 +84,43 @@ public function getDropsForCompatibleTool(Item $item) : array{ public function isAffectedBySilkTouch() : bool{ return true; } + + protected function getContainerViewerCount() : int{ + $enderChest = $this->position->getWorld()->getTile($this->position); + if(!$enderChest instanceof TileEnderChest){ + return 0; + } + return $enderChest->getViewerCount(); + } + + private function updateContainerViewerCount(int $amount) : void{ + $enderChest = $this->position->getWorld()->getTile($this->position); + if($enderChest instanceof TileEnderChest){ + $enderChest->setViewerCount($enderChest->getViewerCount() + $amount); + } + } + + protected function getContainerOpenSound() : Sound{ + return new EnderChestOpenSound(); + } + + protected function getContainerCloseSound() : Sound{ + return new EnderChestCloseSound(); + } + + protected function doContainerAnimation(Position $position, bool $isOpen) : void{ + //event ID is always 1 for a chest + //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)); + } + + public function onContainerOpen() : void{ + $this->updateContainerViewerCount(1); + $this->traitOnContainerOpen(); + } + + public function onContainerClose() : void{ + $this->traitOnContainerClose(); + $this->updateContainerViewerCount(-1); + } } diff --git a/src/block/Furnace.php b/src/block/Furnace.php index 7a64e3cd3e5..2c4433413bd 100644 --- a/src/block/Furnace.php +++ b/src/block/Furnace.php @@ -23,6 +23,7 @@ namespace pocketmine\block; +use pocketmine\block\inventory\window\FurnaceInventoryWindow; use pocketmine\block\tile\Furnace as TileFurnace; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; use pocketmine\block\utils\LightableTrait; @@ -61,7 +62,7 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player 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 0d823674b23..8c65e836c9d 100644 --- a/src/block/Hopper.php +++ b/src/block/Hopper.php @@ -23,6 +23,7 @@ namespace pocketmine\block; +use pocketmine\block\inventory\window\HopperInventoryWindow; use pocketmine\block\tile\Hopper as TileHopper; use pocketmine\block\utils\PoweredByRedstoneTrait; use pocketmine\block\utils\SupportType; @@ -84,7 +85,7 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player 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 d3dd4f3c766..d19fc9449d9 100644 --- a/src/block/Loom.php +++ b/src/block/Loom.php @@ -23,7 +23,7 @@ namespace pocketmine\block; -use pocketmine\block\inventory\LoomInventory; +use pocketmine\block\inventory\window\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 d557401eec3..17003396336 100644 --- a/src/block/ShulkerBox.php +++ b/src/block/ShulkerBox.php @@ -23,16 +23,26 @@ namespace pocketmine\block; +use pocketmine\block\inventory\window\BlockInventoryWindow; use pocketmine\block\tile\ShulkerBox as TileShulkerBox; +use pocketmine\block\utils\AnimatedContainer; +use pocketmine\block\utils\AnimatedContainerTrait; use pocketmine\block\utils\AnyFacingTrait; use pocketmine\block\utils\SupportType; use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\item\Item; use pocketmine\math\Vector3; +use pocketmine\network\mcpe\protocol\BlockEventPacket; +use pocketmine\network\mcpe\protocol\types\BlockPosition; use pocketmine\player\Player; use pocketmine\world\BlockTransaction; +use pocketmine\world\Position; +use pocketmine\world\sound\ShulkerBoxCloseSound; +use pocketmine\world\sound\ShulkerBoxOpenSound; +use pocketmine\world\sound\Sound; -class ShulkerBox extends Opaque{ +class ShulkerBox extends Opaque implements AnimatedContainer{ + use AnimatedContainerTrait; use AnyFacingTrait; protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{ @@ -105,7 +115,7 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player return true; } - $player->setCurrentWindow($shulker->getInventory()); + $player->setCurrentWindow(new BlockInventoryWindow($player, $shulker->getInventory(), $this->position)); } } @@ -115,4 +125,18 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player public function getSupportType(int $facing) : SupportType{ return SupportType::NONE; } + + protected function getContainerOpenSound() : Sound{ + return new ShulkerBoxOpenSound(); + } + + protected function getContainerCloseSound() : Sound{ + return new ShulkerBoxCloseSound(); + } + + protected function doContainerAnimation(Position $position, bool $isOpen) : void{ + //event ID is always 1 for a chest + //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/SmithingTable.php b/src/block/SmithingTable.php index 741e9c02fdc..b96a582d100 100644 --- a/src/block/SmithingTable.php +++ b/src/block/SmithingTable.php @@ -23,7 +23,7 @@ namespace pocketmine\block; -use pocketmine\block\inventory\SmithingTableInventory; +use pocketmine\block\inventory\window\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 30c19d25dcb..3c22e74a83e 100644 --- a/src/block/Stonecutter.php +++ b/src/block/Stonecutter.php @@ -23,7 +23,7 @@ namespace pocketmine\block; -use pocketmine\block\inventory\StonecutterInventory; +use pocketmine\block\inventory\window\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 8720c985b1e..00000000000 --- 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/BarrelInventory.php b/src/block/inventory/BarrelInventory.php deleted file mode 100644 index 0d17d2a3e5b..00000000000 --- a/src/block/inventory/BarrelInventory.php +++ /dev/null @@ -1,57 +0,0 @@ -holder = $holder; - parent::__construct(27); - } - - protected function getOpenSound() : Sound{ - return new BarrelOpenSound(); - } - - protected function getCloseSound() : Sound{ - return new BarrelCloseSound(); - } - - protected function animateBlock(bool $isOpen) : void{ - $holder = $this->getHolder(); - $world = $holder->getWorld(); - $block = $world->getBlock($holder); - if($block instanceof Barrel){ - $world->setBlock($holder, $block->setOpen($isOpen)); - } - } -} diff --git a/src/block/inventory/CampfireInventory.php b/src/block/inventory/CampfireInventory.php index ae762473e23..f3bd618c218 100644 --- a/src/block/inventory/CampfireInventory.php +++ b/src/block/inventory/CampfireInventory.php @@ -24,13 +24,9 @@ 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/CartographyTableInventory.php deleted file mode 100644 index 7bd9146ac6f..00000000000 --- a/src/block/inventory/CartographyTableInventory.php +++ /dev/null @@ -1,37 +0,0 @@ -holder = $holder; - parent::__construct(2); - } -} diff --git a/src/block/inventory/ChestInventory.php b/src/block/inventory/ChestInventory.php deleted file mode 100644 index b61fab57c35..00000000000 --- a/src/block/inventory/ChestInventory.php +++ /dev/null @@ -1,56 +0,0 @@ -holder = $holder; - parent::__construct(27); - } - - protected function getOpenSound() : Sound{ - return new ChestOpenSound(); - } - - protected function getCloseSound() : Sound{ - return new ChestCloseSound(); - } - - public function animateBlock(bool $isOpen) : void{ - $holder = $this->getHolder(); - - //event ID is always 1 for a chest - $holder->getWorld()->broadcastPacketToViewers($holder, BlockEventPacket::create(BlockPosition::fromVector3($holder), 1, $isOpen ? 1 : 0)); - } -} diff --git a/src/block/inventory/DoubleChestInventory.php b/src/block/inventory/DoubleChestInventory.php index a7eb4a4398c..5c11d1a7af7 100644 --- a/src/block/inventory/DoubleChestInventory.php +++ b/src/block/inventory/DoubleChestInventory.php @@ -24,27 +24,17 @@ 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 @@ protected function internalSetContents(array $items) : void{ $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 @@ public function isSlotEmpty(int $index) : bool{ $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/EnchantInventory.php b/src/block/inventory/EnchantInventory.php deleted file mode 100644 index 6dffbad32b7..00000000000 --- a/src/block/inventory/EnchantInventory.php +++ /dev/null @@ -1,88 +0,0 @@ - - */ - private array $options = []; - - public function __construct(Position $holder){ - $this->holder = $holder; - parent::__construct(2); - } - - protected function onSlotChange(int $index, Item $before) : void{ - if($index === self::SLOT_INPUT){ - foreach($this->viewers as $viewer){ - $this->options = []; - $item = $this->getInput(); - $options = Helper::generateOptions($this->holder, $item, $viewer->getEnchantmentSeed()); - - $event = new PlayerEnchantingOptionsRequestEvent($viewer, $this, $options); - $event->call(); - if(!$event->isCancelled() && count($event->getOptions()) > 0){ - $this->options = array_values($event->getOptions()); - $viewer->getNetworkSession()->getInvManager()?->syncEnchantingTableOptions($this->options); - } - } - } - - parent::onSlotChange($index, $before); - } - - public function getInput() : Item{ - return $this->getItem(self::SLOT_INPUT); - } - - public function getLapis() : Item{ - return $this->getItem(self::SLOT_LAPIS); - } - - public function getOutput(int $optionId) : ?Item{ - $option = $this->getOption($optionId); - return $option === null ? null : Helper::enchantItem($this->getInput(), $option->getEnchantments()); - } - - public function getOption(int $optionId) : ?EnchantingOption{ - return $this->options[$optionId] ?? null; - } -} diff --git a/src/block/inventory/EnderChestInventory.php b/src/block/inventory/EnderChestInventory.php deleted file mode 100644 index c1d7c5401d3..00000000000 --- a/src/block/inventory/EnderChestInventory.php +++ /dev/null @@ -1,88 +0,0 @@ -holder = $holder; - } - - public function getEnderInventory() : PlayerEnderInventory{ - return $this->inventory; - } - - public function getViewerCount() : int{ - $enderChest = $this->getHolder()->getWorld()->getTile($this->getHolder()); - if(!$enderChest instanceof EnderChest){ - return 0; - } - return $enderChest->getViewerCount(); - } - - protected function getOpenSound() : Sound{ - return new EnderChestOpenSound(); - } - - protected function getCloseSound() : Sound{ - return new EnderChestCloseSound(); - } - - protected function animateBlock(bool $isOpen) : void{ - $holder = $this->getHolder(); - - //event ID is always 1 for a chest - $holder->getWorld()->broadcastPacketToViewers($holder, BlockEventPacket::create(BlockPosition::fromVector3($holder), 1, $isOpen ? 1 : 0)); - } - - public function onClose(Player $who) : void{ - $this->animatedBlockInventoryTrait_onClose($who); - $enderChest = $this->getHolder()->getWorld()->getTile($this->getHolder()); - if($enderChest instanceof EnderChest){ - $enderChest->setViewerCount($enderChest->getViewerCount() - 1); - } - } -} diff --git a/src/block/inventory/ShulkerBoxInventory.php b/src/block/inventory/ShulkerBoxInventory.php deleted file mode 100644 index d915a995182..00000000000 --- a/src/block/inventory/ShulkerBoxInventory.php +++ /dev/null @@ -1,67 +0,0 @@ -holder = $holder; - parent::__construct(27); - } - - protected function getOpenSound() : Sound{ - return new ShulkerBoxOpenSound(); - } - - protected function getCloseSound() : Sound{ - return new ShulkerBoxCloseSound(); - } - - public function canAddItem(Item $item) : bool{ - $blockTypeId = ItemTypeIds::toBlockTypeId($item->getTypeId()); - if($blockTypeId === BlockTypeIds::SHULKER_BOX || $blockTypeId === BlockTypeIds::DYED_SHULKER_BOX){ - return false; - } - return parent::canAddItem($item); - } - - protected function animateBlock(bool $isOpen) : void{ - $holder = $this->getHolder(); - - //event ID is always 1 for a chest - $holder->getWorld()->broadcastPacketToViewers($holder, BlockEventPacket::create(BlockPosition::fromVector3($holder), 1, $isOpen ? 1 : 0)); - } -} diff --git a/src/block/inventory/AnvilInventory.php b/src/block/inventory/window/AnvilInventoryWindow.php similarity index 71% rename from src/block/inventory/AnvilInventory.php rename to src/block/inventory/window/AnvilInventoryWindow.php index 7d906a6326e..2b994a90da7 100644 --- a/src/block/inventory/AnvilInventory.php +++ b/src/block/inventory/window/AnvilInventoryWindow.php @@ -21,20 +21,21 @@ declare(strict_types=1); -namespace pocketmine\block\inventory; +namespace pocketmine\block\inventory\window; use pocketmine\inventory\SimpleInventory; -use pocketmine\inventory\TemporaryInventory; +use pocketmine\player\Player; +use pocketmine\player\TemporaryInventoryWindow; use pocketmine\world\Position; -class AnvilInventory extends SimpleInventory implements BlockInventory, TemporaryInventory{ - use BlockInventoryTrait; - +final class AnvilInventoryWindow extends BlockInventoryWindow implements TemporaryInventoryWindow{ public const SLOT_INPUT = 0; public const SLOT_MATERIAL = 1; - public function __construct(Position $holder){ - $this->holder = $holder; - parent::__construct(2); + public function __construct( + Player $viewer, + Position $holder + ){ + parent::__construct($viewer, new SimpleInventory(2), $holder); } } diff --git a/src/block/inventory/window/BlockInventoryWindow.php b/src/block/inventory/window/BlockInventoryWindow.php new file mode 100644 index 00000000000..149c2e81426 --- /dev/null +++ b/src/block/inventory/window/BlockInventoryWindow.php @@ -0,0 +1,59 @@ +holder; } + + public function onOpen() : void{ + parent::onOpen(); + $block = $this->holder->getWorld()->getBlock($this->holder); + if($block instanceof AnimatedContainer){ + $block->onContainerOpen(); + } + } + + public function onClose() : void{ + $block = $this->holder->getWorld()->getBlock($this->holder); + if($block instanceof AnimatedContainer){ + $block->onContainerClose(); + } + parent::onClose(); + } +} diff --git a/src/block/inventory/BrewingStandInventory.php b/src/block/inventory/window/BrewingStandInventoryWindow.php similarity index 72% rename from src/block/inventory/BrewingStandInventory.php rename to src/block/inventory/window/BrewingStandInventoryWindow.php index 8bab4ba97b0..ee9af83c653 100644 --- a/src/block/inventory/BrewingStandInventory.php +++ b/src/block/inventory/window/BrewingStandInventoryWindow.php @@ -21,22 +21,12 @@ declare(strict_types=1); -namespace pocketmine\block\inventory; - -use pocketmine\inventory\SimpleInventory; -use pocketmine\world\Position; - -class BrewingStandInventory extends SimpleInventory implements BlockInventory{ - use BlockInventoryTrait; +namespace pocketmine\block\inventory\window; +final class BrewingStandInventoryWindow extends BlockInventoryWindow{ public const SLOT_INGREDIENT = 0; public const SLOT_BOTTLE_LEFT = 1; public const SLOT_BOTTLE_MIDDLE = 2; public const SLOT_BOTTLE_RIGHT = 3; public const SLOT_FUEL = 4; - - public function __construct(Position $holder, int $size = 5){ - $this->holder = $holder; - parent::__construct($size); - } } diff --git a/src/block/inventory/SmithingTableInventory.php b/src/block/inventory/window/CartographyTableInventoryWindow.php similarity index 68% rename from src/block/inventory/SmithingTableInventory.php rename to src/block/inventory/window/CartographyTableInventoryWindow.php index 2f67ac9d2dd..22d2d5c430f 100644 --- a/src/block/inventory/SmithingTableInventory.php +++ b/src/block/inventory/window/CartographyTableInventoryWindow.php @@ -21,17 +21,19 @@ declare(strict_types=1); -namespace pocketmine\block\inventory; +namespace pocketmine\block\inventory\window; use pocketmine\inventory\SimpleInventory; -use pocketmine\inventory\TemporaryInventory; +use pocketmine\player\Player; +use pocketmine\player\TemporaryInventoryWindow; use pocketmine\world\Position; -final class SmithingTableInventory extends SimpleInventory implements BlockInventory, TemporaryInventory{ - use BlockInventoryTrait; +final class CartographyTableInventoryWindow extends BlockInventoryWindow implements TemporaryInventoryWindow{ - public function __construct(Position $holder){ - $this->holder = $holder; - parent::__construct(3); + public function __construct( + Player $viewer, + Position $holder + ){ + parent::__construct($viewer, new SimpleInventory(2), $holder); } } diff --git a/src/block/inventory/CraftingTableInventory.php b/src/block/inventory/window/CraftingTableInventoryWindow.php similarity index 68% rename from src/block/inventory/CraftingTableInventory.php rename to src/block/inventory/window/CraftingTableInventoryWindow.php index 767e8a5f49c..46e5485e5fb 100644 --- a/src/block/inventory/CraftingTableInventory.php +++ b/src/block/inventory/window/CraftingTableInventoryWindow.php @@ -21,17 +21,19 @@ declare(strict_types=1); -namespace pocketmine\block\inventory; +namespace pocketmine\block\inventory\window; use pocketmine\crafting\CraftingGrid; -use pocketmine\inventory\TemporaryInventory; +use pocketmine\player\Player; use pocketmine\world\Position; -final class CraftingTableInventory extends CraftingGrid implements BlockInventory, TemporaryInventory{ - use BlockInventoryTrait; +final class CraftingTableInventoryWindow extends BlockInventoryWindow{ - public function __construct(Position $holder){ - $this->holder = $holder; - parent::__construct(CraftingGrid::SIZE_BIG); + public function __construct( + Player $viewer, + Position $holder + ){ + //TODO: generics would be good for this, since it has special methods + parent::__construct($viewer, new CraftingGrid(CraftingGrid::SIZE_BIG), $holder); } } diff --git a/src/inventory/PlayerCursorInventory.php b/src/block/inventory/window/DoubleChestInventoryWindow.php similarity index 63% rename from src/inventory/PlayerCursorInventory.php rename to src/block/inventory/window/DoubleChestInventoryWindow.php index 30f9e3aec25..801a6ff9d0f 100644 --- a/src/inventory/PlayerCursorInventory.php +++ b/src/block/inventory/window/DoubleChestInventoryWindow.php @@ -21,18 +21,24 @@ declare(strict_types=1); -namespace pocketmine\inventory; +namespace pocketmine\block\inventory\window; +use pocketmine\inventory\Inventory; use pocketmine\player\Player; +use pocketmine\world\Position; + +final class DoubleChestInventoryWindow extends BlockInventoryWindow{ -class PlayerCursorInventory extends SimpleInventory implements TemporaryInventory{ public function __construct( - protected Player $holder + Player $viewer, + Inventory $inventory, + private Position $left, + private Position $right ){ - parent::__construct(1); + parent::__construct($viewer, $inventory, $this->left); } - public function getHolder() : Player{ - return $this->holder; - } + public function getLeft() : Position{ return $this->left; } + + public function getRight() : Position{ return $this->right; } } diff --git a/src/block/inventory/window/EnchantingTableInventoryWindow.php b/src/block/inventory/window/EnchantingTableInventoryWindow.php new file mode 100644 index 00000000000..294c1818908 --- /dev/null +++ b/src/block/inventory/window/EnchantingTableInventoryWindow.php @@ -0,0 +1,104 @@ + $weakThis */ + $weakThis = \WeakReference::create($this); + $this->listener = new CallbackInventoryListener( + onSlotChange: static function(Inventory $_, int $slot) use ($weakThis) : void{ //remaining params unneeded + if($slot === self::SLOT_INPUT && ($strongThis = $weakThis->get()) !== null){ + $strongThis->regenerateOptions(); + } + }, + onContentChange: static function() use ($weakThis) : void{ + if(($strongThis = $weakThis->get()) !== null){ + $strongThis->regenerateOptions(); + } + } + ); + $this->inventory->getListeners()->add($this->listener); + } + + public function __destruct(){ + $this->inventory->getListeners()->remove($this->listener); + } + + private function regenerateOptions() : void{ + $this->options = []; + $item = $this->getInput(); + $options = Helper::generateOptions($this->holder, $item, $this->viewer->getEnchantmentSeed()); + + $event = new PlayerEnchantingOptionsRequestEvent($this->viewer, $this, $options); + $event->call(); + if(!$event->isCancelled() && count($event->getOptions()) > 0){ + $this->options = array_values($event->getOptions()); + $this->viewer->getNetworkSession()->getInvManager()?->syncEnchantingTableOptions($this->options); + } + } + + public function getInput() : Item{ + return $this->inventory->getItem(self::SLOT_INPUT); + } + + public function getLapis() : Item{ + return $this->inventory->getItem(self::SLOT_LAPIS); + } + + public function getOutput(int $optionId) : ?Item{ + $option = $this->getOption($optionId); + return $option === null ? null : Helper::enchantItem($this->getInput(), $option->getEnchantments()); + } + + public function getOption(int $optionId) : ?EnchantingOption{ + return $this->options[$optionId] ?? null; + } +} diff --git a/src/block/inventory/FurnaceInventory.php b/src/block/inventory/window/FurnaceInventoryWindow.php similarity index 69% rename from src/block/inventory/FurnaceInventory.php rename to src/block/inventory/window/FurnaceInventoryWindow.php index ff44d6b7063..11d36324c76 100644 --- a/src/block/inventory/FurnaceInventory.php +++ b/src/block/inventory/window/FurnaceInventoryWindow.php @@ -21,51 +21,51 @@ declare(strict_types=1); -namespace pocketmine\block\inventory; +namespace pocketmine\block\inventory\window; use pocketmine\crafting\FurnaceType; -use pocketmine\inventory\SimpleInventory; +use pocketmine\inventory\Inventory; use pocketmine\item\Item; +use pocketmine\player\Player; use pocketmine\world\Position; -class FurnaceInventory extends SimpleInventory implements BlockInventory{ - use BlockInventoryTrait; - +final class FurnaceInventoryWindow extends BlockInventoryWindow{ public const SLOT_INPUT = 0; public const SLOT_FUEL = 1; public const SLOT_RESULT = 2; public function __construct( + Player $viewer, + Inventory $inventory, Position $holder, private FurnaceType $furnaceType ){ - $this->holder = $holder; - parent::__construct(3); + parent::__construct($viewer, $inventory, $holder); } public function getFurnaceType() : FurnaceType{ return $this->furnaceType; } public function getResult() : Item{ - return $this->getItem(self::SLOT_RESULT); + return $this->inventory->getItem(self::SLOT_RESULT); } public function getFuel() : Item{ - return $this->getItem(self::SLOT_FUEL); + return $this->inventory->getItem(self::SLOT_FUEL); } public function getSmelting() : Item{ - return $this->getItem(self::SLOT_INPUT); + return $this->inventory->getItem(self::SLOT_INPUT); } public function setResult(Item $item) : void{ - $this->setItem(self::SLOT_RESULT, $item); + $this->inventory->setItem(self::SLOT_RESULT, $item); } public function setFuel(Item $item) : void{ - $this->setItem(self::SLOT_FUEL, $item); + $this->inventory->setItem(self::SLOT_FUEL, $item); } public function setSmelting(Item $item) : void{ - $this->setItem(self::SLOT_INPUT, $item); + $this->inventory->setItem(self::SLOT_INPUT, $item); } } diff --git a/src/block/inventory/BlockInventory.php b/src/block/inventory/window/HopperInventoryWindow.php similarity index 84% rename from src/block/inventory/BlockInventory.php rename to src/block/inventory/window/HopperInventoryWindow.php index 063a451a9ab..b89f688720f 100644 --- a/src/block/inventory/BlockInventory.php +++ b/src/block/inventory/window/HopperInventoryWindow.php @@ -21,10 +21,8 @@ declare(strict_types=1); -namespace pocketmine\block\inventory; +namespace pocketmine\block\inventory\window; -use pocketmine\world\Position; +final class HopperInventoryWindow extends BlockInventoryWindow{ -interface BlockInventory{ - public function getHolder() : Position; } diff --git a/src/block/inventory/LoomInventory.php b/src/block/inventory/window/LoomInventoryWindow.php similarity index 71% rename from src/block/inventory/LoomInventory.php rename to src/block/inventory/window/LoomInventoryWindow.php index fd34620a05f..ceb27ac1ac6 100644 --- a/src/block/inventory/LoomInventory.php +++ b/src/block/inventory/window/LoomInventoryWindow.php @@ -21,21 +21,23 @@ declare(strict_types=1); -namespace pocketmine\block\inventory; +namespace pocketmine\block\inventory\window; use pocketmine\inventory\SimpleInventory; -use pocketmine\inventory\TemporaryInventory; +use pocketmine\player\Player; +use pocketmine\player\TemporaryInventoryWindow; use pocketmine\world\Position; -final class LoomInventory extends SimpleInventory implements BlockInventory, TemporaryInventory{ - use BlockInventoryTrait; +final class LoomInventoryWindow extends BlockInventoryWindow implements TemporaryInventoryWindow{ public const SLOT_BANNER = 0; public const SLOT_DYE = 1; public const SLOT_PATTERN = 2; - public function __construct(Position $holder, int $size = 3){ - $this->holder = $holder; - parent::__construct($size); + public function __construct( + Player $viewer, + Position $holder + ){ + parent::__construct($viewer, new SimpleInventory(3), $holder); } } diff --git a/src/block/inventory/HopperInventory.php b/src/block/inventory/window/SmithingTableInventoryWindow.php similarity index 69% rename from src/block/inventory/HopperInventory.php rename to src/block/inventory/window/SmithingTableInventoryWindow.php index a20e9ae1a18..7cb850ef0be 100644 --- a/src/block/inventory/HopperInventory.php +++ b/src/block/inventory/window/SmithingTableInventoryWindow.php @@ -21,16 +21,15 @@ declare(strict_types=1); -namespace pocketmine\block\inventory; +namespace pocketmine\block\inventory\window; use pocketmine\inventory\SimpleInventory; +use pocketmine\player\Player; +use pocketmine\player\TemporaryInventoryWindow; use pocketmine\world\Position; -class HopperInventory extends SimpleInventory implements BlockInventory{ - use BlockInventoryTrait; - - public function __construct(Position $holder, int $size = 5){ - $this->holder = $holder; - parent::__construct($size); +final class SmithingTableInventoryWindow extends BlockInventoryWindow implements TemporaryInventoryWindow{ + public function __construct(Player $viewer, Position $holder){ + parent::__construct($viewer, new SimpleInventory(3), $holder); } } diff --git a/src/block/inventory/StonecutterInventory.php b/src/block/inventory/window/StonecutterInventoryWindow.php similarity index 70% rename from src/block/inventory/StonecutterInventory.php rename to src/block/inventory/window/StonecutterInventoryWindow.php index 4ed644ff2c3..a7a29a97430 100644 --- a/src/block/inventory/StonecutterInventory.php +++ b/src/block/inventory/window/StonecutterInventoryWindow.php @@ -21,19 +21,17 @@ declare(strict_types=1); -namespace pocketmine\block\inventory; +namespace pocketmine\block\inventory\window; use pocketmine\inventory\SimpleInventory; -use pocketmine\inventory\TemporaryInventory; +use pocketmine\player\Player; +use pocketmine\player\TemporaryInventoryWindow; use pocketmine\world\Position; -class StonecutterInventory extends SimpleInventory implements BlockInventory, TemporaryInventory{ - use BlockInventoryTrait; - +final class StonecutterInventoryWindow extends BlockInventoryWindow implements TemporaryInventoryWindow{ public const SLOT_INPUT = 0; - public function __construct(Position $holder){ - $this->holder = $holder; - parent::__construct(1); + public function __construct(Player $viewer, Position $holder){ + parent::__construct($viewer, new SimpleInventory(1), $holder); } } diff --git a/src/block/tile/Barrel.php b/src/block/tile/Barrel.php index a7f3532142b..9b64bbf321d 100644 --- a/src/block/tile/Barrel.php +++ b/src/block/tile/Barrel.php @@ -23,7 +23,8 @@ 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,7 @@ public function close() : void{ } } - public function getInventory() : BarrelInventory{ - return $this->inventory; - } - - public function getRealInventory() : BarrelInventory{ + public function getInventory() : Inventory{ return $this->inventory; } diff --git a/src/block/tile/BrewingStand.php b/src/block/tile/BrewingStand.php index c3a331e6c63..87b53eefbcb 100644 --- a/src/block/tile/BrewingStand.php +++ b/src/block/tile/BrewingStand.php @@ -23,12 +23,13 @@ namespace pocketmine\block\tile; -use pocketmine\block\inventory\BrewingStandInventory; +use pocketmine\block\inventory\window\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,7 @@ public function close() : void{ } } - public function getInventory() : BrewingStandInventory{ - return $this->inventory; - } - - public function getRealInventory() : BrewingStandInventory{ + public function getInventory() : Inventory{ return $this->inventory; } @@ -132,7 +129,7 @@ private function checkFuel(Item $item) : void{ } $item->pop(); - $this->inventory->setItem(BrewingStandInventory::SLOT_FUEL, $item); + $this->inventory->setItem(BrewingStandInventoryWindow::SLOT_FUEL, $item); $this->maxFuelTime = $this->remainingFuelTime = $ev->getFuelTime(); } @@ -142,14 +139,14 @@ private function checkFuel(Item $item) : void{ * @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 +173,8 @@ public function onUpdate() : bool{ $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 +216,7 @@ public function onUpdate() : bool{ } $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 ad4a193d7b8..a9ad30351f6 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); @@ -68,10 +68,6 @@ public function getInventory() : CampfireInventory{ return $this->inventory; } - public function getRealInventory() : CampfireInventory{ - return $this->inventory; - } - /** * @return int[] * @phpstan-return array diff --git a/src/block/tile/Chest.php b/src/block/tile/Chest.php index 4f97eed234a..9bc3fdffb3c 100644 --- a/src/block/tile/Chest.php +++ b/src/block/tile/Chest.php @@ -23,8 +23,9 @@ 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,15 +115,14 @@ protected function onBlockDestroyedHook() : void{ $this->containerTraitBlockDestroyedHook(); } - public function getInventory() : ChestInventory|DoubleChestInventory{ - if($this->isPaired() && $this->doubleInventory === null){ - $this->checkPairing(); - } - return $this->doubleInventory instanceof DoubleChestInventory ? $this->doubleInventory : $this->inventory; + public function getInventory() : Inventory{ + return $this->inventory; } - public function getRealInventory() : ChestInventory{ - return $this->inventory; + public function getDoubleInventory() : ?DoubleChestInventory{ return $this->doubleInventory; } + + public function setDoubleInventory(?DoubleChestInventory $doubleChestInventory) : void{ + $this->doubleInventory = $doubleChestInventory; } protected function checkPairing() : void{ @@ -133,18 +133,7 @@ protected function checkPairing() : void{ }elseif(($pair = $this->getPair()) instanceof Chest){ if(!$pair->isPaired()){ $pair->createPair($this); - $pair->checkPairing(); - } - if($this->doubleInventory === null){ - if($pair->doubleInventory !== null){ - $this->doubleInventory = $pair->doubleInventory; - }else{ - if(($pair->position->x + ($pair->position->z << 15)) > ($this->position->x + ($this->position->z << 15))){ //Order them correctly - $this->doubleInventory = $pair->doubleInventory = new DoubleChestInventory($pair->inventory, $this->inventory); - }else{ - $this->doubleInventory = $pair->doubleInventory = new DoubleChestInventory($this->inventory, $pair->inventory); - } - } + $this->doubleInventory = $pair->doubleInventory = null; } }else{ $this->doubleInventory = null; diff --git a/src/block/tile/ChiseledBookshelf.php b/src/block/tile/ChiseledBookshelf.php index 06175e27f4d..da7e42c0df4 100644 --- a/src/block/tile/ChiseledBookshelf.php +++ b/src/block/tile/ChiseledBookshelf.php @@ -55,10 +55,6 @@ public function getInventory() : SimpleInventory{ return $this->inventory; } - public function getRealInventory() : SimpleInventory{ - return $this->inventory; - } - public function getLastInteractedSlot() : ?ChiseledBookshelfSlot{ return $this->lastInteractedSlot; } @@ -87,7 +83,7 @@ protected function writeSaveData(CompoundTag $nbt) : void{ protected function loadItems(CompoundTag $tag) : void{ if(($inventoryTag = $tag->getTag(Container::TAG_ITEMS)) instanceof ListTag && $inventoryTag->getTagType() === NBT::TAG_Compound){ - $inventory = $this->getRealInventory(); + $inventory = $this->inventory; $listeners = $inventory->getListeners()->toArray(); $inventory->getListeners()->remove(...$listeners); //prevent any events being fired by initialization @@ -118,7 +114,7 @@ protected function loadItems(CompoundTag $tag) : void{ protected function saveItems(CompoundTag $tag) : void{ $items = []; - foreach($this->getRealInventory()->getContents(true) as $slot => $item){ + foreach($this->inventory->getContents(true) as $slot => $item){ if($item->isNull()){ $items[$slot] = CompoundTag::create() ->setByte(SavedItemStackData::TAG_COUNT, 0) diff --git a/src/block/tile/Container.php b/src/block/tile/Container.php index dd257dd9c5b..1c4e37fec43 100644 --- a/src/block/tile/Container.php +++ b/src/block/tile/Container.php @@ -23,15 +23,12 @@ namespace pocketmine\block\tile; -use pocketmine\inventory\Inventory; use pocketmine\inventory\InventoryHolder; interface Container extends InventoryHolder{ public const TAG_ITEMS = "Items"; public const TAG_LOCK = "Lock"; - public function getRealInventory() : Inventory; - /** * Returns whether this container can be opened by an item with the given custom name. */ diff --git a/src/block/tile/ContainerTrait.php b/src/block/tile/ContainerTrait.php index fdd050a4169..cf69f37b513 100644 --- a/src/block/tile/ContainerTrait.php +++ b/src/block/tile/ContainerTrait.php @@ -25,7 +25,6 @@ use pocketmine\data\bedrock\item\SavedItemStackData; use pocketmine\data\SavedDataLoadingException; -use pocketmine\inventory\Inventory; use pocketmine\item\Item; use pocketmine\nbt\NBT; use pocketmine\nbt\tag\CompoundTag; @@ -40,11 +39,9 @@ trait ContainerTrait{ /** @var string|null */ private $lock = null; - abstract public function getRealInventory() : Inventory; - protected function loadItems(CompoundTag $tag) : void{ if(($inventoryTag = $tag->getTag(Container::TAG_ITEMS)) instanceof ListTag && $inventoryTag->getTagType() === NBT::TAG_Compound){ - $inventory = $this->getRealInventory(); + $inventory = $this->getInventory(); $listeners = $inventory->getListeners()->toArray(); $inventory->getListeners()->remove(...$listeners); //prevent any events being fired by initialization @@ -71,7 +68,7 @@ protected function loadItems(CompoundTag $tag) : void{ protected function saveItems(CompoundTag $tag) : void{ $items = []; - foreach($this->getRealInventory()->getContents() as $slot => $item){ + foreach($this->getInventory()->getContents() as $slot => $item){ $items[] = $item->nbtSerialize($slot); } @@ -98,7 +95,7 @@ abstract protected function getPosition() : Position; * @see Tile::onBlockDestroyedHook() */ protected function onBlockDestroyedHook() : void{ - $inv = $this->getRealInventory(); + $inv = $this->getInventory(); $pos = $this->getPosition(); $world = $pos->getWorld(); diff --git a/src/block/tile/Furnace.php b/src/block/tile/Furnace.php index a706a827e74..e036a606e3f 100644 --- a/src/block/tile/Furnace.php +++ b/src/block/tile/Furnace.php @@ -24,13 +24,14 @@ namespace pocketmine\block\tile; use pocketmine\block\Furnace as BlockFurnace; -use pocketmine\block\inventory\FurnaceInventory; +use pocketmine\block\inventory\window\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,14 +105,10 @@ public function close() : void{ } } - public function getInventory() : FurnaceInventory{ + public function getInventory() : Inventory{ return $this->inventory; } - public function getRealInventory() : FurnaceInventory{ - return $this->getInventory(); - } - protected function checkFuel(Item $fuel) : void{ $ev = new FurnaceBurnEvent($this, $fuel, $fuel->getFuelTime()); $ev->call(); @@ -123,7 +120,7 @@ protected function checkFuel(Item $fuel) : void{ $this->onStartSmelting(); if($this->remainingFuelTime > 0 && $ev->isBurning()){ - $this->inventory->setFuel($fuel->getFuelResidue()); + $this->inventory->setItem(FurnaceInventoryWindow::SLOT_FUEL, $fuel->getFuelResidue()); } } @@ -159,9 +156,9 @@ public function onUpdate() : bool{ $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 +181,9 @@ public function onUpdate() : bool{ $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 5c39bc2bd5a..4a427332356 100644 --- a/src/block/tile/Hopper.php +++ b/src/block/tile/Hopper.php @@ -23,7 +23,8 @@ 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,7 @@ public function getDefaultName() : string{ return "Hopper"; } - public function getInventory() : HopperInventory{ - return $this->inventory; - } - - public function getRealInventory() : HopperInventory{ + public function getInventory() : Inventory{ return $this->inventory; } } diff --git a/src/block/tile/ShulkerBox.php b/src/block/tile/ShulkerBox.php index a30b75c4ea8..2e75659157f 100644 --- a/src/block/tile/ShulkerBox.php +++ b/src/block/tile/ShulkerBox.php @@ -23,8 +23,13 @@ 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,7 @@ public function setFacing(int $facing) : void{ $this->facing = $facing; } - public function getInventory() : ShulkerBoxInventory{ - return $this->inventory; - } - - public function getRealInventory() : ShulkerBoxInventory{ + public function getInventory() : Inventory{ return $this->inventory; } diff --git a/src/block/inventory/BlockInventoryTrait.php b/src/block/utils/AnimatedContainer.php similarity index 61% rename from src/block/inventory/BlockInventoryTrait.php rename to src/block/utils/AnimatedContainer.php index 980e947f02b..9af901f18c0 100644 --- a/src/block/inventory/BlockInventoryTrait.php +++ b/src/block/utils/AnimatedContainer.php @@ -21,14 +21,18 @@ declare(strict_types=1); -namespace pocketmine\block\inventory; +namespace pocketmine\block\utils; -use pocketmine\world\Position; +interface AnimatedContainer{ + /** + * Do actions when the container block is opened by a player. + * If you have a custom viewer counter (like ender chests), you should increment it here. + */ + public function onContainerOpen() : void; -trait BlockInventoryTrait{ - protected Position $holder; - - public function getHolder() : Position{ - return $this->holder; - } + /** + * Do actions when the container block is closed by a player. + * As above, you should decrement your custom viewer counter here, if you have one. + */ + public function onContainerClose() : void; } diff --git a/src/block/utils/AnimatedContainerTrait.php b/src/block/utils/AnimatedContainerTrait.php new file mode 100644 index 00000000000..ab1337b1b85 --- /dev/null +++ b/src/block/utils/AnimatedContainerTrait.php @@ -0,0 +1,71 @@ +getPosition(); + $tile = $position->getWorld()->getTile($position); + if($tile instanceof InventoryHolder){ + return count($tile->getInventory()->getViewers()); + } + return 0; + } + + abstract protected function getContainerOpenSound() : Sound; + + abstract protected function getContainerCloseSound() : Sound; + + abstract protected function doContainerAnimation(Position $position, bool $isOpen) : void; + + protected function playContainerSound(Position $position, bool $isOpen) : void{ + $position->getWorld()->addSound($position->add(0.5, 0.5, 0.5), $isOpen ? $this->getContainerOpenSound() : $this->getContainerCloseSound()); + } + + abstract protected function getPosition() : Position; + + protected function doContainerEffects(bool $isOpen) : void{ + $position = $this->getPosition(); + $this->doContainerAnimation($position, $isOpen); + $this->playContainerSound($position, $isOpen); + } + + public function onContainerOpen() : void{ + if($this->getContainerViewerCount() === 1){ + $this->doContainerEffects(true); + } + } + + public function onContainerClose() : void{ + if($this->getContainerViewerCount() === 1){ + $this->doContainerEffects(false); + } + } +} diff --git a/src/block/utils/BrewingStandSlot.php b/src/block/utils/BrewingStandSlot.php index c7d74d8da03..51f0df50190 100644 --- a/src/block/utils/BrewingStandSlot.php +++ b/src/block/utils/BrewingStandSlot.php @@ -23,7 +23,7 @@ namespace pocketmine\block\utils; -use pocketmine\block\inventory\BrewingStandInventory; +use pocketmine\block\inventory\window\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/command/defaults/EnchantCommand.php b/src/command/defaults/EnchantCommand.php index 191a146b036..6b73aad6c41 100644 --- a/src/command/defaults/EnchantCommand.php +++ b/src/command/defaults/EnchantCommand.php @@ -56,7 +56,7 @@ public function execute(CommandSender $sender, string $commandLabel, array $args return true; } - $item = $player->getInventory()->getItemInHand(); + $item = $player->getHotbar()->getHeldItem(); if($item->isNull()){ $sender->sendMessage(KnownTranslationFactory::commands_enchant_noItem()); @@ -79,7 +79,7 @@ public function execute(CommandSender $sender, string $commandLabel, array $args //this is necessary to deal with enchanted books, which are a different item type than regular books $enchantedItem = EnchantingHelper::enchantItem($item, [new EnchantmentInstance($enchantment, $level)]); - $player->getInventory()->setItemInHand($enchantedItem); + $player->getHotbar()->setHeldItem($enchantedItem); self::broadcastCommandMessage($sender, KnownTranslationFactory::commands_enchant_success($player->getName())); return true; diff --git a/src/crafting/CraftingGrid.php b/src/crafting/CraftingGrid.php index a41b5e3a73d..47cca94baed 100644 --- a/src/crafting/CraftingGrid.php +++ b/src/crafting/CraftingGrid.php @@ -29,7 +29,7 @@ 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/ExperienceManager.php b/src/entity/ExperienceManager.php index 9cff48f330c..5e04728f8c5 100644 --- a/src/entity/ExperienceManager.php +++ b/src/entity/ExperienceManager.php @@ -243,7 +243,7 @@ public function onPickupXp(int $xpValue) : void{ //TODO: replace this with a more generic equipment getting/setting interface $equipment = []; - if(($item = $this->entity->getInventory()->getItemInHand()) instanceof Durable && $item->hasEnchantment(VanillaEnchantments::MENDING())){ + if(($item = $this->entity->getHotbar()->getHeldItem()) instanceof Durable && $item->hasEnchantment(VanillaEnchantments::MENDING())){ $equipment[$mainHandIndex] = $item; } if(($item = $this->entity->getOffHandInventory()->getItem(0)) instanceof Durable && $item->hasEnchantment(VanillaEnchantments::MENDING())){ @@ -263,7 +263,7 @@ public function onPickupXp(int $xpValue) : void{ $xpValue -= (int) ceil($repairAmount / 2); if($k === $mainHandIndex){ - $this->entity->getInventory()->setItemInHand($repairItem); + $this->entity->getHotbar()->setHeldItem($repairItem); }elseif($k === $offHandIndex){ $this->entity->getOffHandInventory()->setItem(0, $repairItem); }else{ diff --git a/src/entity/Human.php b/src/entity/Human.php index 006bf6df864..5f7f577027e 100644 --- a/src/entity/Human.php +++ b/src/entity/Human.php @@ -32,11 +32,10 @@ use pocketmine\event\entity\EntityDamageEvent; use pocketmine\event\player\PlayerExhaustEvent; 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; @@ -100,9 +99,10 @@ class Human extends Living implements ProjectileSource, InventoryHolder{ public function getNetworkTypeId() : string{ return EntityIds::PLAYER; } - protected PlayerInventory $inventory; - protected PlayerOffHandInventory $offHandInventory; - protected PlayerEnderInventory $enderInventory; + protected Hotbar $hotbar; + protected Inventory $inventory; + protected Inventory $offHandInventory; + protected Inventory $enderInventory; protected UuidInterface $uuid; @@ -228,13 +228,17 @@ public function getXpDropAmount() : int{ return min(100, 7 * $this->xpManager->getXpLevel()); } - public function getInventory() : PlayerInventory{ + public function getHotbar() : Hotbar{ + return $this->hotbar; + } + + 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; } @@ -265,25 +269,27 @@ protected function initEntity(CompoundTag $nbt) : void{ $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( $this->getViewers(), fn(EntityEventBroadcaster $broadcaster, array $recipients) => $broadcaster->onMobMainHandItemChange($recipients, $this) ); $this->inventory->getListeners()->add(new CallbackInventoryListener( function(Inventory $unused, int $slot, Item $unused2) use ($syncHeldItem) : void{ - if($slot === $this->inventory->getHeldItemIndex()){ + if($slot === $this->hotbar->getSelectedIndex()){ $syncHeldItem(); } }, function(Inventory $unused, array $oldItems) use ($syncHeldItem) : void{ - if(array_key_exists($this->inventory->getHeldItemIndex(), $oldItems)){ + if(array_key_exists($this->hotbar->getSelectedIndex(), $oldItems)){ $syncHeldItem(); } } )); - $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); @@ -326,8 +332,9 @@ function(Inventory $unused, array $oldItems) use ($syncHeldItem) : void{ self::populateInventoryFromListTag($this->enderInventory, $enderChestInventoryItems); } - $this->inventory->setHeldItemIndex($nbt->getInt(self::TAG_SELECTED_INVENTORY_SLOT, 0)); - $this->inventory->getHeldItemIndexChangeListeners()->add(fn() => NetworkBroadcastUtils::broadcastEntityEvent( + $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) )); @@ -367,7 +374,7 @@ public function applyDamageModifiers(EntityDamageEvent $source) : void{ $type = $source->getCause(); if($type !== EntityDamageEvent::CAUSE_SUICIDE && $type !== EntityDamageEvent::CAUSE_VOID - && ($this->inventory->getItemInHand() instanceof Totem || $this->offHandInventory->getItem(0) instanceof Totem)){ + && ($this->hotbar->getHeldItem() instanceof Totem || $this->offHandInventory->getItem(0) instanceof Totem)){ $compensation = $this->getHealth() - $source->getFinalDamage() - 1; if($compensation <= -1){ @@ -389,10 +396,10 @@ protected function applyPostDamageEffects(EntityDamageEvent $source) : void{ $this->broadcastAnimation(new TotemUseAnimation($this)); $this->broadcastSound(new TotemUseSound()); - $hand = $this->inventory->getItemInHand(); + $hand = $this->hotbar->getHeldItem(); if($hand instanceof Totem){ $hand->pop(); //Plugins could alter max stack size - $this->inventory->setItemInHand($hand); + $this->hotbar->setHeldItem($hand); }elseif(($offHand = $this->offHandInventory->getItem(0)) instanceof Totem){ $offHand->pop(); $this->offHandInventory->setItem(0, $offHand); @@ -425,8 +432,8 @@ public function saveNBT() : CompoundTag{ $nbt->setTag(self::TAG_INVENTORY, $inventoryTag); //Normal inventory - $slotCount = $this->inventory->getSize() + $this->inventory->getHotbarSize(); - for($slot = $this->inventory->getHotbarSize(); $slot < $slotCount; ++$slot){ + $slotCount = $this->inventory->getSize() + $this->hotbar->getSize(); + for($slot = $this->hotbar->getSize(); $slot < $slotCount; ++$slot){ $item = $this->inventory->getItem($slot - 9); if(!$item->isNull()){ $inventoryTag->push($item->nbtSerialize($slot)); @@ -441,7 +448,7 @@ public function saveNBT() : CompoundTag{ } } - $nbt->setInt(self::TAG_SELECTED_INVENTORY_SLOT, $this->inventory->getHeldItemIndex()); + $nbt->setInt(self::TAG_SELECTED_INVENTORY_SLOT, $this->hotbar->getSelectedIndex()); $offHandItem = $this->offHandInventory->getItem(0); if(!$offHandItem->isNull()){ @@ -495,7 +502,7 @@ protected function sendSpawnPacket(Player $player) : void{ $this->location->pitch, $this->location->yaw, $this->location->yaw, //TODO: head yaw - ItemStackWrapper::legacy($typeConverter->coreItemStackToNet($this->getInventory()->getItemInHand())), + ItemStackWrapper::legacy($typeConverter->coreItemStackToNet($this->hotbar->getHeldItem())), GameMode::SURVIVAL, $this->getAllNetworkData(), new PropertySyncData([], []), @@ -529,8 +536,8 @@ public function getOffsetPosition(Vector3 $vector3) : Vector3{ } protected function onDispose() : void{ + $this->hotbar->getSelectedIndexChangeListeners()->clear(); $this->inventory->removeAllViewers(); - $this->inventory->getHeldItemIndexChangeListeners()->clear(); $this->offHandInventory->removeAllViewers(); $this->enderInventory->removeAllViewers(); parent::onDispose(); @@ -538,9 +545,6 @@ protected function onDispose() : void{ 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 81f46424f17..faed09ef157 100644 --- a/src/entity/Living.php +++ b/src/entity/Living.php @@ -145,7 +145,7 @@ protected function initEntity(CompoundTag $nbt) : void{ $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 @@ protected function onDispose() : void{ 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 4401a71264b..be5415e27f9 100644 --- a/src/event/inventory/InventoryCloseEvent.php +++ b/src/event/inventory/InventoryCloseEvent.php @@ -23,12 +23,12 @@ 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 4202946791b..30250505ba8 100644 --- a/src/event/inventory/InventoryEvent.php +++ b/src/event/inventory/InventoryEvent.php @@ -27,15 +27,15 @@ 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 @@ public function getInventory() : Inventory{ * @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 f45056bf458..44275b671b3 100644 --- a/src/event/inventory/InventoryOpenEvent.php +++ b/src/event/inventory/InventoryOpenEvent.php @@ -25,14 +25,14 @@ 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 833185f7605..14e0ed73c4a 100644 --- a/src/event/player/PlayerEnchantingOptionsRequestEvent.php +++ b/src/event/player/PlayerEnchantingOptionsRequestEvent.php @@ -23,10 +23,9 @@ namespace pocketmine\event\player; -use pocketmine\block\inventory\EnchantInventory; +use pocketmine\block\inventory\window\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 8591cc65bf3..6985f460a61 100644 --- a/src/inventory/ArmorInventory.php +++ b/src/inventory/ArmorInventory.php @@ -24,7 +24,6 @@ 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 0d5d1ffe602..adf8d3ff747 100644 --- a/src/inventory/BaseInventory.php +++ b/src/inventory/BaseInventory.php @@ -102,28 +102,14 @@ public function setContents(array $items) : void{ $listeners = $this->listeners->toArray(); $this->listeners->clear(); - $viewers = $this->viewers; - $this->viewers = []; $this->internalSetContents($items); $this->listeners->add(...$listeners); //don't directly write, in case listeners were added while operation was in progress - foreach($viewers as $id => $viewer){ - $this->viewers[$id] = $viewer; - } $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(); @@ -351,7 +337,7 @@ public function getViewers() : array{ */ 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]); @@ -370,13 +356,6 @@ protected function onSlotChange(int $index, Item $before) : void{ foreach($this->listeners as $listener){ $listener->onSlotChange($this, $index, $before); } - foreach($this->viewers as $viewer){ - $invManager = $viewer->getNetworkSession()->getInvManager(); - if($invManager === null){ - continue; - } - $invManager->onSlotChange($this, $index); - } } /** @@ -387,14 +366,6 @@ protected function onContentChange(array $itemsBefore) : void{ foreach($this->listeners as $listener){ $listener->onContentChange($this, $itemsBefore); } - - foreach($this->getViewers() as $viewer){ - $invManager = $viewer->getNetworkSession()->getInvManager(); - if($invManager === null){ - continue; - } - $invManager->syncContents($this); - } } public function slotExists(int $slot) : bool{ diff --git a/src/inventory/DelegateInventory.php b/src/inventory/DelegateInventory.php deleted file mode 100644 index a211732cf46..00000000000 --- a/src/inventory/DelegateInventory.php +++ /dev/null @@ -1,103 +0,0 @@ -backingInventory->getListeners()->add($this->inventoryListener = new CallbackInventoryListener( - static function(Inventory $unused, int $slot, Item $oldItem) use ($weakThis) : void{ - if(($strongThis = $weakThis->get()) !== null){ - $strongThis->backingInventoryChanging = true; - try{ - $strongThis->onSlotChange($slot, $oldItem); - }finally{ - $strongThis->backingInventoryChanging = false; - } - } - }, - static function(Inventory $unused, array $oldContents) use ($weakThis) : void{ - if(($strongThis = $weakThis->get()) !== null){ - $strongThis->backingInventoryChanging = true; - try{ - $strongThis->onContentChange($oldContents); - }finally{ - $strongThis->backingInventoryChanging = false; - } - } - } - )); - } - - public function __destruct(){ - $this->backingInventory->getListeners()->remove($this->inventoryListener); - } - - public function getSize() : int{ - return $this->backingInventory->getSize(); - } - - public function getItem(int $index) : Item{ - return $this->backingInventory->getItem($index); - } - - protected function internalSetItem(int $index, Item $item) : void{ - $this->backingInventory->setItem($index, $item); - } - - public function getContents(bool $includeEmpty = false) : array{ - return $this->backingInventory->getContents($includeEmpty); - } - - protected function internalSetContents(array $items) : void{ - $this->backingInventory->setContents($items); - } - - public function isSlotEmpty(int $index) : bool{ - return $this->backingInventory->isSlotEmpty($index); - } - - protected function onSlotChange(int $index, Item $before) : void{ - if($this->backingInventoryChanging){ - parent::onSlotChange($index, $before); - } - } - - protected function onContentChange(array $itemsBefore) : void{ - if($this->backingInventoryChanging){ - parent::onContentChange($itemsBefore); - } - } -} diff --git a/src/inventory/PlayerInventory.php b/src/inventory/Hotbar.php similarity index 63% rename from src/inventory/PlayerInventory.php rename to src/inventory/Hotbar.php index fdaa0adff88..a98a868d458 100644 --- a/src/inventory/PlayerInventory.php +++ b/src/inventory/Hotbar.php @@ -23,30 +23,30 @@ namespace pocketmine\inventory; -use pocketmine\entity\Human; use pocketmine\item\Item; -use pocketmine\player\Player; use pocketmine\utils\ObjectSet; -class PlayerInventory extends SimpleInventory{ - - protected Human $holder; - protected int $itemInHandIndex = 0; +final class Hotbar{ + protected int $selectedIndex = 0; /** * @var \Closure[]|ObjectSet * @phpstan-var ObjectSet<\Closure(int $oldIndex) : void> */ - protected ObjectSet $heldItemIndexChangeListeners; - - public function __construct(Human $player){ - $this->holder = $player; - $this->heldItemIndexChangeListeners = new ObjectSet(); - parent::__construct(36); + protected ObjectSet $selectedIndexChangeListeners; + + public function __construct( + private Inventory $inventory, + private int $size = 9 + ){ + if($this->inventory->getSize() < $this->size){ + throw new \InvalidArgumentException("Inventory size must be at least $this->size"); + } + $this->selectedIndexChangeListeners = new ObjectSet(); } public function isHotbarSlot(int $slot) : bool{ - return $slot >= 0 && $slot < $this->getHotbarSize(); + return $slot >= 0 && $slot < $this->getSize(); } /** @@ -54,7 +54,7 @@ public function isHotbarSlot(int $slot) : bool{ */ private function throwIfNotHotbarSlot(int $slot) : void{ if(!$this->isHotbarSlot($slot)){ - throw new \InvalidArgumentException("$slot is not a valid hotbar slot index (expected 0 - " . ($this->getHotbarSize() - 1) . ")"); + throw new \InvalidArgumentException("$slot is not a valid hotbar slot index (expected 0 - " . ($this->getSize() - 1) . ")"); } } @@ -65,14 +65,14 @@ private function throwIfNotHotbarSlot(int $slot) : void{ */ public function getHotbarSlotItem(int $hotbarSlot) : Item{ $this->throwIfNotHotbarSlot($hotbarSlot); - return $this->getItem($hotbarSlot); + return $this->inventory->getItem($hotbarSlot); } /** * Returns the hotbar slot number the holder is currently holding. */ - public function getHeldItemIndex() : int{ - return $this->itemInHandIndex; + public function getSelectedIndex() : int{ + return $this->selectedIndex; } /** @@ -82,13 +82,13 @@ public function getHeldItemIndex() : int{ * * @throws \InvalidArgumentException if the hotbar slot is out of range */ - public function setHeldItemIndex(int $hotbarSlot) : void{ + public function setSelectedIndex(int $hotbarSlot) : void{ $this->throwIfNotHotbarSlot($hotbarSlot); - $oldIndex = $this->itemInHandIndex; - $this->itemInHandIndex = $hotbarSlot; + $oldIndex = $this->selectedIndex; + $this->selectedIndex = $hotbarSlot; - foreach($this->heldItemIndexChangeListeners as $callback){ + foreach($this->selectedIndexChangeListeners as $callback){ $callback($oldIndex); } } @@ -97,30 +97,26 @@ public function setHeldItemIndex(int $hotbarSlot) : void{ * @return \Closure[]|ObjectSet * @phpstan-return ObjectSet<\Closure(int $oldIndex) : void> */ - public function getHeldItemIndexChangeListeners() : ObjectSet{ return $this->heldItemIndexChangeListeners; } + public function getSelectedIndexChangeListeners() : ObjectSet{ return $this->selectedIndexChangeListeners; } /** * Returns the currently-held item. */ - public function getItemInHand() : Item{ - return $this->getHotbarSlotItem($this->itemInHandIndex); + public function getHeldItem() : Item{ + return $this->getHotbarSlotItem($this->selectedIndex); } /** * Sets the item in the currently-held slot to the specified item. */ - public function setItemInHand(Item $item) : void{ - $this->setItem($this->getHeldItemIndex(), $item); + public function setHeldItem(Item $item) : void{ + $this->inventory->setItem($this->getSelectedIndex(), $item); } /** * Returns the number of slots in the hotbar. */ - public function getHotbarSize() : int{ - return 9; - } - - public function getHolder() : Human{ - return $this->holder; + public function getSize() : int{ + return $this->size; } } diff --git a/src/inventory/Inventory.php b/src/inventory/Inventory.php index 5c81d7d9c86..de460ca2927 100644 --- a/src/inventory/Inventory.php +++ b/src/inventory/Inventory.php @@ -98,6 +98,13 @@ public function canAddItem(Item $item) : bool; */ 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 @@ public function swap(int $slot1, int $slot2) : void; */ 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 75752c9e716..00000000000 --- a/src/inventory/PlayerCraftingInventory.php +++ /dev/null @@ -1,36 +0,0 @@ -holder; } -} diff --git a/src/inventory/PlayerOffHandInventory.php b/src/inventory/PlayerOffHandInventory.php deleted file mode 100644 index 127b09f98a6..00000000000 --- 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 4b44326fa17..a6a3393227b 100644 --- a/src/inventory/SimpleInventory.php +++ b/src/inventory/SimpleInventory.php @@ -84,7 +84,7 @@ protected function internalSetContents(array $items) : void{ } } - 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 47290e40151..bc9621314f6 100644 --- a/src/inventory/transaction/InventoryTransaction.php +++ b/src/inventory/transaction/InventoryTransaction.php @@ -28,6 +28,7 @@ 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 pocketmine\utils\Utils; use function array_keys; @@ -59,10 +60,10 @@ class InventoryTransaction{ protected bool $hasExecuted = false; /** - * @var Inventory[] - * @phpstan-var array + * @var InventoryWindow[] + * @phpstan-var array */ - protected array $inventories = []; + protected array $inventoryWindows = []; /** * @var InventoryAction[] @@ -87,11 +88,11 @@ public function getSource() : Player{ } /** - * @return Inventory[] - * @phpstan-return array + * @return InventoryWindow[] + * @phpstan-return array */ - public function getInventories() : array{ - return $this->inventories; + public function getInventoryWindows() : array{ + return $this->inventoryWindows; } /** @@ -111,8 +112,8 @@ public function addAction(InventoryAction $action) : void{ if(!isset($this->actions[$hash = spl_object_id($action)])){ $this->actions[$hash] = $action; $action->onAddToTransaction($this); - if($action instanceof SlotChangeAction && !isset($this->inventories[$inventoryId = spl_object_id($action->getInventory())])){ - $this->inventories[$inventoryId] = $action->getInventory(); + if($action instanceof SlotChangeAction && !isset($this->inventoryWindows[$inventoryId = spl_object_id($action->getInventoryWindow())])){ + $this->inventoryWindows[$inventoryId] = $action->getInventoryWindow(); } }else{ throw new \InvalidArgumentException("Tried to add the same action to a transaction twice"); @@ -196,8 +197,8 @@ protected function squashDuplicateSlotChanges() : void{ 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(); } } @@ -207,10 +208,11 @@ protected function squashDuplicateSlotChanges() : void{ 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); @@ -225,7 +227,7 @@ protected function squashDuplicateSlotChanges() : void{ 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 95b6c4a147c..7fe2490d9a7 100644 --- a/src/inventory/transaction/TransactionBuilderInventory.php +++ b/src/inventory/transaction/SlotChangeActionBuilder.php @@ -28,6 +28,7 @@ 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 @@ * 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 @@ protected function internalSetContents(array $items) : void{ } 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,16 +93,25 @@ public function getContents(bool $includeEmpty = false) : array{ 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[] */ public function generateActions() : array{ $result = []; + $inventory = $this->inventoryWindow->getInventory(); foreach($this->changedSlots as $index => $newItem){ if($newItem !== null){ - $oldItem = $this->actualInventory->getItem($index); + $oldItem = $inventory->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 f56b2aaa160..6232a44a4d5 100644 --- a/src/inventory/transaction/TransactionBuilder.php +++ b/src/inventory/transaction/TransactionBuilder.php @@ -23,13 +23,13 @@ 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 @@ public function addAction(InventoryAction $action) : void{ $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 3c9b8e5cf79..553a640087b 100644 --- a/src/inventory/transaction/action/SlotChangeAction.php +++ b/src/inventory/transaction/action/SlotChangeAction.php @@ -27,6 +27,7 @@ use pocketmine\inventory\SlotValidatedInventory; use pocketmine\inventory\transaction\TransactionValidationException; use pocketmine\item\Item; +use pocketmine\player\InventoryWindow; use pocketmine\player\Player; /** @@ -34,7 +35,7 @@ */ class SlotChangeAction extends InventoryAction{ public function __construct( - protected Inventory $inventory, + protected InventoryWindow $inventoryWindow, private int $inventorySlot, Item $sourceItem, Item $targetItem @@ -43,10 +44,10 @@ public function __construct( } /** - * 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; } /** @@ -62,21 +63,22 @@ public function getSlot() : int{ * @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); } @@ -88,6 +90,6 @@ public function validate(Player $source) : void{ * 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/item/Armor.php b/src/item/Armor.php index 417c57f75ca..9e1046d9629 100644 --- a/src/item/Armor.php +++ b/src/item/Armor.php @@ -145,7 +145,7 @@ public function onClickAir(Player $player, Vector3 $directionVector, array &$ret $thisCopy = clone $this; $new = $thisCopy->pop(); $player->getArmorInventory()->setItem($this->getArmorSlot(), $new); - $player->getInventory()->setItemInHand($existing); + $player->getHotbar()->setHeldItem($existing); $sound = $new->getMaterial()->getEquipSound(); if($sound !== null){ $player->broadcastSound($sound); 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 dfd3e999a47..fa457e14f68 100644 --- a/src/network/mcpe/ComplexInventoryMapEntry.php +++ b/src/network/mcpe/ComplexWindowMapEntry.php @@ -23,9 +23,9 @@ 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 @@ public function __construct( } } - 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 7df8c734be6..c54a2fc229a 100644 --- a/src/network/mcpe/InventoryManager.php +++ b/src/network/mcpe/InventoryManager.php @@ -23,24 +23,26 @@ 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\window\AnvilInventoryWindow; +use pocketmine\block\inventory\window\BlockInventoryWindow; +use pocketmine\block\inventory\window\BrewingStandInventoryWindow; +use pocketmine\block\inventory\window\CartographyTableInventoryWindow; +use pocketmine\block\inventory\window\CraftingTableInventoryWindow; +use pocketmine\block\inventory\window\EnchantingTableInventoryWindow; +use pocketmine\block\inventory\window\FurnaceInventoryWindow; +use pocketmine\block\inventory\window\HopperInventoryWindow; +use pocketmine\block\inventory\window\LoomInventoryWindow; +use pocketmine\block\inventory\window\SmithingTableInventoryWindow; +use pocketmine\block\inventory\window\StonecutterInventoryWindow; use pocketmine\crafting\FurnaceType; use pocketmine\data\bedrock\EnchantmentIdMap; use pocketmine\inventory\Inventory; +use pocketmine\inventory\InventoryListener; use pocketmine\inventory\transaction\action\SlotChangeAction; use pocketmine\inventory\transaction\InventoryTransaction; use pocketmine\item\enchantment\EnchantingOption; use pocketmine\item\enchantment\EnchantmentInstance; +use pocketmine\item\Item; use pocketmine\network\mcpe\cache\CreativeInventoryCache; use pocketmine\network\mcpe\protocol\ClientboundPacket; use pocketmine\network\mcpe\protocol\ContainerClosePacket; @@ -61,7 +63,9 @@ 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; @@ -76,27 +80,27 @@ 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{ +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; @@ -126,33 +130,48 @@ public function __construct( $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->getInventory()->getHeldItemIndexChangeListeners()->add($this->syncSelectedHotbarSlot(...)); + $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 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, Inventory $inventory) : void{ - if(isset($this->inventories[spl_object_id($inventory)])){ - throw new \InvalidArgumentException("Inventory " . get_class($inventory) . " is already tracked"); + 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); - $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; @@ -162,17 +181,19 @@ private function addDynamic(Inventory $inventory) : int{ * @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 ); + $window->getInventory()->getListeners()->add($this); foreach($complexSlotMap->getSlotMap() as $netSlot => $coreSlot){ - $this->complexSlotToInventoryMap[$netSlot] = $complexSlotMap; + $this->complexSlotToWindowMap[$netSlot] = $complexSlotMap; } } @@ -180,7 +201,7 @@ private function addComplex(array|int $slotMap, Inventory $inventory) : void{ * @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); @@ -188,48 +209,52 @@ private function addComplexDynamic(array|int $slotMap, Inventory $inventory) : i } private function remove(int $id) : void{ - $inventory = $this->networkIdToInventoryMap[$id]; - unset($this->networkIdToInventoryMap[$id]); - if($this->getWindowId($inventory) === null){ - unset($this->inventories[spl_object_id($inventory)]); - foreach($this->complexSlotToInventoryMap as $netSlot => $entry){ - if($entry->getInventory() === $inventory){ - unset($this->complexSlotToInventoryMap[$netSlot]); + $window = $this->networkIdToWindowMap[$id]; + $inventory = $window->getInventory(); + unset($this->networkIdToWindowMap[$id]); + if($this->getWindowId($window) === null){ + $inventory->getListeners()->remove($this); + 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{ @@ -238,7 +263,7 @@ public function addTransactionPredictedSlotChanges(InventoryTransaction $tx) : v 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); } } } @@ -266,8 +291,8 @@ public function addRawPredictedSlotChanges(array $networkInventoryActions) : voi continue; } - [$inventory, $slot] = $info; - $this->addPredictedSlotChange($inventory, $slot, $action->newItem->getItemStack()); + [$window, $slot] = $info; + $this->addPredictedSlotChange($window, $slot, $action->newItem->getItemStack()); } } @@ -302,32 +327,32 @@ private function openWindowDeferred(\Closure $func) : void{ * @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){ @@ -338,7 +363,7 @@ public function onCurrentWindowChange(Inventory $inventory) : void{ $this->session->sendDataPacket($pk); } $this->currentWindowType = $windowType ?? WindowTypes::CONTAINER; - $this->syncContents($inventory); + $this->syncContents($window); return; } } @@ -353,27 +378,27 @@ public function getContainerOpenCallbacks() : ObjectSet{ return $this->container * @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)]; @@ -386,7 +411,8 @@ public function onClientOpenMainInventory() : void{ $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( @@ -398,25 +424,25 @@ public function onClientOpenMainInventory() : void{ } 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 @@ -468,13 +494,25 @@ private function itemStacksEqual(ItemStack $left, ItemStack $right) : bool{ $this->itemStackExtraDataEqual($left, $right); } - public function onSlotChange(Inventory $inventory, int $slot) : void{ - $inventoryEntry = $this->inventories[spl_object_id($inventory)] ?? null; + public function onSlotChange(Inventory $inventory, int $slot, Item $oldItem) : void{ + $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)){ @@ -502,7 +540,7 @@ private function sendInventorySlotPackets(int $windowId, int $netSlot, ItemStack $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()) )); @@ -511,7 +549,7 @@ private function sendInventorySlotPackets(int $windowId, int $netSlot, ItemStack $this->session->sendDataPacket(InventorySlotPacket::create( $windowId, $netSlot, - new FullContainerName($this->lastInventoryNetworkId), + new FullContainerName($this->lastWindowNetworkId), new ItemStackWrapper(0, ItemStack::null()), $itemStackWrapper )); @@ -532,18 +570,15 @@ private function sendInventoryContentPackets(int $windowId, array $itemStackWrap $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"); @@ -552,7 +587,7 @@ public function syncSlot(Inventory $inventory, int $slot, ItemStack $itemStack) $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; } @@ -570,8 +605,18 @@ public function syncSlot(Inventory $inventory, int $slot, ItemStack $itemStack) unset($entry->predictions[$slot], $entry->pendingSyncs[$slot]); } - public function syncContents(Inventory $inventory) : void{ - $entry = $this->inventories[spl_object_id($inventory)] ?? null; + public function onContentChange(Inventory $inventory, array $oldContents) : void{ + //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); + } + } + + private function syncContents(InventoryWindow $window) : void{ + $inventory = $window->getInventory(); + $entry = $this->getEntry($inventory); if($entry === null){ //this can happen when an inventory changed during InventoryCloseEvent, or when a temporary inventory //is cleared before removal. @@ -580,7 +625,7 @@ public function syncContents(Inventory $inventory) : void{ if($entry->complexSlotMap !== null){ $windowId = ContainerIds::UI; }else{ - $windowId = $this->getWindowId($inventory); + $windowId = $this->getWindowId($window); } if($windowId !== null){ $entry->predictions = []; @@ -607,8 +652,8 @@ public function syncContents(Inventory $inventory) : void{ } public function syncAll() : void{ - foreach($this->inventories as $entry){ - $this->syncContents($entry->inventory); + foreach($this->entries as $entry){ + $this->syncContents($entry->window); } } @@ -618,8 +663,8 @@ public function requestSyncAll() : void{ 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 ??? @@ -637,14 +682,14 @@ public function syncMismatchedPredictedSlotChanges() : void{ 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); @@ -655,7 +700,13 @@ public function flushPendingUpdates() : void{ } 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)); } @@ -667,12 +718,9 @@ public function onClientSelectHotbarSlot(int $slot) : void{ public function syncSelectedHotbarSlot() : void{ $playerInventory = $this->player->getInventory(); - $selected = $playerInventory->getHeldItemIndex(); + $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"); @@ -680,7 +728,7 @@ public function syncSelectedHotbarSlot() : void{ $this->session->sendDataPacket(MobEquipmentPacket::create( $this->player->getId(), - new ItemStackWrapper($itemStackInfo->getStackId(), $this->session->getTypeConverter()->coreItemStackToNet($playerInventory->getItemInHand())), + new ItemStackWrapper($itemStackInfo->getStackId(), $this->session->getTypeConverter()->coreItemStackToNet($playerInventory->getItem($selected))), $selected, $selected, ContainerIds::INVENTORY @@ -732,8 +780,7 @@ private function newItemStackId() : int{ } 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 deb2e8e4db4..8f7c07de232 100644 --- a/src/network/mcpe/InventoryManagerEntry.php +++ b/src/network/mcpe/InventoryManagerEntry.php @@ -23,8 +23,8 @@ 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/StandardEntityEventBroadcaster.php b/src/network/mcpe/StandardEntityEventBroadcaster.php index 3e2df399483..b19a631dc9d 100644 --- a/src/network/mcpe/StandardEntityEventBroadcaster.php +++ b/src/network/mcpe/StandardEntityEventBroadcaster.php @@ -103,12 +103,12 @@ public function onEntityRemoved(array $recipients, Entity $entity) : void{ public function onMobMainHandItemChange(array $recipients, Human $mob) : void{ //TODO: we could send zero for slot here because remote players don't need to know which slot was selected - $inv = $mob->getInventory(); + $inv = $mob->getHotbar(); $this->sendDataPacket($recipients, MobEquipmentPacket::create( $mob->getId(), - ItemStackWrapper::legacy($this->typeConverter->coreItemStackToNet($inv->getItemInHand())), - $inv->getHeldItemIndex(), - $inv->getHeldItemIndex(), + ItemStackWrapper::legacy($this->typeConverter->coreItemStackToNet($inv->getHeldItem())), + $inv->getSelectedIndex(), + $inv->getSelectedIndex(), ContainerIds::INVENTORY )); } diff --git a/src/network/mcpe/handler/InGamePacketHandler.php b/src/network/mcpe/handler/InGamePacketHandler.php index 6c7402b5293..1d65f14bbf2 100644 --- a/src/network/mcpe/handler/InGamePacketHandler.php +++ b/src/network/mcpe/handler/InGamePacketHandler.php @@ -304,11 +304,11 @@ public function handleActorEvent(ActorEventPacket $packet) : bool{ switch($packet->eventId){ case ActorEvent::EATING_ITEM: //TODO: ignore this and handle it server-side - $item = $this->player->getInventory()->getItemInHand(); + $item = $this->player->getHotbar()->getHeldItem(); if($item->isNull()){ return false; } - $this->player->broadcastAnimation(new ConsumingItemAnimation($this->player, $this->player->getInventory()->getItemInHand())); + $this->player->broadcastAnimation(new ConsumingItemAnimation($this->player, $item)); break; default: return false; @@ -355,7 +355,7 @@ public function handleInventoryTransaction(InventoryTransactionPacket $packet) : [$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 +461,8 @@ private function handleNormalTransaction(NormalTransactionData $data, int $itemS $droppedItem = $sourceSlotItem->pop($droppedCount); $builder = new TransactionBuilder(); - $builder->getInventory($inventory)->setItem($sourceSlot, $sourceSlotItem); + $window = $this->inventoryManager->getInventoryWindow($inventory) ?? throw new AssumptionFailedError("This should never happen"); + $builder->getActionBuilder($window)->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 6db8f1e12b8..2e2138c291c 100644 --- a/src/network/mcpe/handler/ItemStackRequestExecutor.php +++ b/src/network/mcpe/handler/ItemStackRequestExecutor.php @@ -23,16 +23,15 @@ namespace pocketmine\network\mcpe\handler; -use pocketmine\block\inventory\EnchantInventory; -use pocketmine\inventory\Inventory; +use pocketmine\block\inventory\window\EnchantingTableInventoryWindow; use pocketmine\inventory\transaction\action\CreateItemAction; use pocketmine\inventory\transaction\action\DestroyItemAction; 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 +51,7 @@ 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 pocketmine\utils\Utils; @@ -82,25 +82,22 @@ public function __construct( $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") ); @@ -108,7 +105,7 @@ private function matchItemStack(Inventory $inventory, int $slotId, int $clientIt } /** - * @phpstan-return array{TransactionBuilderInventory, int} + * @phpstan-return array{SlotChangeActionBuilder, int} * * @throws ItemStackRequestProcessException */ @@ -118,16 +115,17 @@ protected function getBuilderInventoryAndSlot(ItemStackRequestSlotInfo $info) : 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]; } /** @@ -152,12 +150,12 @@ protected function removeItemFromSlot(ItemStackRequestSlotInfo $slotInfo, int $c [$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); @@ -175,12 +173,12 @@ protected function addItemToSlot(ItemStackRequestSlotInfo $slotInfo, Item $item, [$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 @@ -337,7 +335,7 @@ protected function processItemStackRequestAction(ItemStackRequestAction $action) $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 1369e3ba722..75c740a5f28 100644 --- a/src/network/mcpe/handler/ItemStackResponseBuilder.php +++ b/src/network/mcpe/handler/ItemStackResponseBuilder.php @@ -59,7 +59,8 @@ private function getInventoryAndSlot(int $containerInterfaceId, int $slotId) : ? if($windowAndSlot === null){ return null; } - [$inventory, $slot] = $windowAndSlot; + [$window, $slot] = $windowAndSlot; + $inventory = $window->getInventory(); if(!$inventory->slotExists($slot)){ return null; } diff --git a/src/inventory/PlayerEnderInventory.php b/src/player/InventoryWindow.php similarity index 62% rename from src/inventory/PlayerEnderInventory.php rename to src/player/InventoryWindow.php index c10c42b12ee..78dcef64af7 100644 --- a/src/inventory/PlayerEnderInventory.php +++ b/src/player/InventoryWindow.php @@ -21,17 +21,30 @@ declare(strict_types=1); -namespace pocketmine\inventory; +namespace pocketmine\player; -use pocketmine\entity\Human; +use pocketmine\inventory\Inventory; + +abstract class InventoryWindow{ -final class PlayerEnderInventory extends SimpleInventory{ public function __construct( - private Human $holder, - int $size = 27 - ){ - parent::__construct($size); + protected Player $viewer, + protected Inventory $inventory + ){} + + public function getViewer() : Player{ + return $this->viewer; + } + + public function getInventory() : Inventory{ + return $this->inventory; } - public function getHolder() : Human{ return $this->holder; } + public function onOpen() : void{ + $this->inventory->onOpen($this->viewer); + } + + public function onClose() : void{ + $this->inventory->onClose($this->viewer); + } } diff --git a/src/player/Player.php b/src/player/Player.php index 858ad6bf54a..e87fb662d6f 100644 --- a/src/player/Player.php +++ b/src/player/Player.php @@ -85,9 +85,7 @@ 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 @@ public static function isValidUserName(?string $name) : bool{ 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; @@ -347,7 +345,7 @@ protected function initHumanData(CompoundTag $nbt) : void{ } private function callDummyItemHeldEvent() : void{ - $slot = $this->inventory->getHeldItemIndex(); + $slot = $this->hotbar->getSelectedIndex(); $event = new PlayerItemHeldEvent($this, $this->inventory->getItem($slot), $slot); $event->call(); @@ -362,7 +360,7 @@ protected function initEntity(CompoundTag $nbt) : void{ $this->inventory->getListeners()->add(new CallbackInventoryListener( function(Inventory $unused, int $slot) : void{ - if($slot === $this->inventory->getHeldItemIndex()){ + if($slot === $this->hotbar->getSelectedIndex()){ $this->setUsingItem(false); $this->callDummyItemHeldEvent(); @@ -1540,10 +1538,10 @@ public function chat(string $message) : bool{ } public function selectHotbarSlot(int $hotbarSlot) : bool{ - if(!$this->inventory->isHotbarSlot($hotbarSlot)){ //TODO: exception here? + if(!$this->hotbar->isHotbarSlot($hotbarSlot)){ //TODO: exception here? return false; } - if($hotbarSlot === $this->inventory->getHeldItemIndex()){ + if($hotbarSlot === $this->hotbar->getSelectedIndex()){ return true; } @@ -1553,7 +1551,7 @@ public function selectHotbarSlot(int $hotbarSlot) : bool{ return false; } - $this->inventory->setHeldItemIndex($hotbarSlot); + $this->hotbar->setSelectedIndex($hotbarSlot); $this->setUsingItem(false); return true; @@ -1565,7 +1563,7 @@ public function selectHotbarSlot(int $hotbarSlot) : bool{ private function returnItemsFromAction(Item $oldHeldItem, Item $newHeldItem, array $extraReturnedItems) : void{ $heldItemChanged = false; - if(!$newHeldItem->equalsExact($oldHeldItem) && $oldHeldItem->equalsExact($this->inventory->getItemInHand())){ + if(!$newHeldItem->equalsExact($oldHeldItem) && $oldHeldItem->equalsExact($this->hotbar->getHeldItem())){ //determine if the item was changed in some meaningful way, or just damaged/changed count //if it was really changed we always need to set it, whether we have finite resources or not $newReplica = clone $oldHeldItem; @@ -1579,7 +1577,7 @@ private function returnItemsFromAction(Item $oldHeldItem, Item $newHeldItem, arr if($newHeldItem instanceof Durable && $newHeldItem->isBroken()){ $this->broadcastSound(new ItemBreakSound()); } - $this->inventory->setItemInHand($newHeldItem); + $this->hotbar->setHeldItem($newHeldItem); $heldItemChanged = true; } } @@ -1589,7 +1587,7 @@ private function returnItemsFromAction(Item $oldHeldItem, Item $newHeldItem, arr } if($heldItemChanged && count($extraReturnedItems) > 0 && $newHeldItem->isNull()){ - $this->inventory->setItemInHand(array_shift($extraReturnedItems)); + $this->hotbar->setHeldItem(array_shift($extraReturnedItems)); } foreach($this->inventory->addItem(...$extraReturnedItems) as $drop){ //TODO: we can't generate a transaction for this since the items aren't coming from an inventory :( @@ -1611,7 +1609,7 @@ private function returnItemsFromAction(Item $oldHeldItem, Item $newHeldItem, arr */ public function useHeldItem() : bool{ $directionVector = $this->getDirectionVector(); - $item = $this->inventory->getItemInHand(); + $item = $this->hotbar->getHeldItem(); $oldItem = clone $item; $ev = new PlayerItemUseEvent($this, $item, $directionVector); @@ -1645,7 +1643,7 @@ public function useHeldItem() : bool{ * @return bool if the consumption succeeded. */ public function consumeHeldItem() : bool{ - $slot = $this->inventory->getItemInHand(); + $slot = $this->hotbar->getHeldItem(); if($slot instanceof ConsumableItem){ $oldItem = clone $slot; @@ -1678,7 +1676,7 @@ public function consumeHeldItem() : bool{ */ public function releaseHeldItem() : bool{ try{ - $item = $this->inventory->getItemInHand(); + $item = $this->hotbar->getHeldItem(); if(!$this->isUsingItem() || $this->hasItemCooldown($item)){ return false; } @@ -1748,21 +1746,21 @@ public function pickEntity(int $entityId) : bool{ private function equipOrAddPickedItem(int $existingSlot, Item $item) : void{ if($existingSlot !== -1){ - if($existingSlot < $this->inventory->getHotbarSize()){ - $this->inventory->setHeldItemIndex($existingSlot); + if($existingSlot < $this->hotbar->getSize()){ + $this->hotbar->setSelectedIndex($existingSlot); }else{ - $this->inventory->swap($this->inventory->getHeldItemIndex(), $existingSlot); + $this->inventory->swap($this->hotbar->getSelectedIndex(), $existingSlot); } }else{ $firstEmpty = $this->inventory->firstEmpty(); if($firstEmpty === -1){ //full inventory - $this->inventory->setItemInHand($item); - }elseif($firstEmpty < $this->inventory->getHotbarSize()){ + $this->hotbar->setHeldItem($item); + }elseif($firstEmpty < $this->hotbar->getSize()){ $this->inventory->setItem($firstEmpty, $item); - $this->inventory->setHeldItemIndex($firstEmpty); + $this->hotbar->setSelectedIndex($firstEmpty); }else{ - $this->inventory->swap($this->inventory->getHeldItemIndex(), $firstEmpty); - $this->inventory->setItemInHand($item); + $this->inventory->swap($this->hotbar->getSelectedIndex(), $firstEmpty); + $this->hotbar->setHeldItem($item); } } } @@ -1779,7 +1777,7 @@ public function attackBlock(Vector3 $pos, int $face) : bool{ $target = $this->getWorld()->getBlock($pos); - $ev = new PlayerInteractEvent($this, $this->inventory->getItemInHand(), $target, null, $face, PlayerInteractEvent::LEFT_CLICK_BLOCK); + $ev = new PlayerInteractEvent($this, $this->hotbar->getHeldItem(), $target, null, $face, PlayerInteractEvent::LEFT_CLICK_BLOCK); if($this->isSpectator()){ $ev->cancel(); } @@ -1788,7 +1786,7 @@ public function attackBlock(Vector3 $pos, int $face) : bool{ return false; } $this->broadcastAnimation(new ArmSwingAnimation($this), $this->getViewers()); - if($target->onAttack($this->inventory->getItemInHand(), $face, $this)){ + if($target->onAttack($this->hotbar->getHeldItem(), $face, $this)){ return true; } @@ -1829,7 +1827,7 @@ public function breakBlock(Vector3 $pos) : bool{ if($this->canInteract($pos->add(0.5, 0.5, 0.5), $this->isCreative() ? self::MAX_REACH_DISTANCE_CREATIVE : self::MAX_REACH_DISTANCE_SURVIVAL)){ $this->broadcastAnimation(new ArmSwingAnimation($this), $this->getViewers()); $this->stopBreakBlock($pos); - $item = $this->inventory->getItemInHand(); + $item = $this->hotbar->getHeldItem(); $oldItem = clone $item; $returnedItems = []; if($this->getWorld()->useBreakOn($pos, $item, $this, true, $returnedItems)){ @@ -1854,7 +1852,7 @@ public function interactBlock(Vector3 $pos, int $face, Vector3 $clickOffset) : b if($this->canInteract($pos->add(0.5, 0.5, 0.5), $this->isCreative() ? self::MAX_REACH_DISTANCE_CREATIVE : self::MAX_REACH_DISTANCE_SURVIVAL)){ $this->broadcastAnimation(new ArmSwingAnimation($this), $this->getViewers()); - $item = $this->inventory->getItemInHand(); //this is a copy of the real item + $item = $this->hotbar->getHeldItem(); //this is a copy of the real item $oldItem = clone $item; $returnedItems = []; if($this->getWorld()->useItemOn($pos, $item, $face, $clickOffset, $this, true, $returnedItems)){ @@ -1883,7 +1881,7 @@ public function attackEntity(Entity $entity) : bool{ return false; } - $heldItem = $this->inventory->getItemInHand(); + $heldItem = $this->hotbar->getHeldItem(); $oldItem = clone $heldItem; $ev = new EntityDamageByEntityEvent($this, $entity, EntityDamageEvent::CAUSE_ENTITY_ATTACK, $heldItem->getAttackPoints()); @@ -1969,15 +1967,15 @@ public function interactEntity(Entity $entity, Vector3 $clickPos) : bool{ $ev->call(); - $item = $this->inventory->getItemInHand(); + $item = $this->hotbar->getHeldItem(); $oldItem = clone $item; if(!$ev->isCancelled()){ if($item->onInteractEntity($this, $entity, $clickPos)){ - if($this->hasFiniteResources() && !$item->equalsExact($oldItem) && $oldItem->equalsExact($this->inventory->getItemInHand())){ + if($this->hasFiniteResources() && !$item->equalsExact($oldItem) && $oldItem->equalsExact($this->hotbar->getHeldItem())){ if($item instanceof Durable && $item->isBroken()){ $this->broadcastSound(new ItemBreakSound()); } - $this->inventory->setItemInHand($item); + $this->hotbar->setHeldItem($item); } } return $entity->onInteract($this, $clickPos); @@ -2318,7 +2316,7 @@ public function onPostDisconnect(Translatable|string $reason, Translatable|strin $this->loadQueue = []; $this->removeCurrentWindow(); - $this->removePermanentInventories(); + $this->removePermanentWindows(); $this->perm->getPermissionRecalculationCallbacks()->clear(); @@ -2334,8 +2332,6 @@ protected function onDispose() : void{ protected function destroyCycles() : void{ $this->networkSession = null; - unset($this->cursorInventory); - unset($this->craftingGrid); $this->spawnPosition = null; $this->blockBreakHandler = null; parent::destroyCycles(); @@ -2405,8 +2401,8 @@ protected function onDeath() : void{ $this->getWorld()->dropItem($this->location, $item); } + $this->hotbar->setSelectedIndex(0); $clearInventory = fn(Inventory $inventory) => $inventory->setContents(array_filter($inventory->getContents(), fn(Item $item) => $item->keepOnDeath())); - $this->inventory->setHeldItemIndex(0); $clearInventory($this->inventory); $clearInventory($this->armorInventory); $clearInventory($this->offHandInventory); @@ -2589,15 +2585,19 @@ public function teleport(Vector3 $pos, ?float $yaw = null, ?float $pitch = null) } 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 @@ public function setCreativeInventory(CreativeInventory $inventory) : void{ * 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 @@ private function doCloseInventory() : void{ $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,21 @@ private function doCloseInventory() : void{ /** * 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); + if($window->getViewer() !== $this){ + throw new \InvalidArgumentException("Cannot reuse InventoryWindow instances, please create a new one for each player"); + } + $ev = new InventoryOpenEvent($window, $this); $ev->call(); if($ev->isCancelled()){ return false; @@ -2689,10 +2707,10 @@ public function setCurrentWindow(Inventory $inventory) : bool{ 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 +2719,7 @@ public function removeCurrentWindow() : void{ 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 +2728,31 @@ public function removeCurrentWindow() : void{ } } - 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/player/PlayerInventoryWindow.php b/src/player/PlayerInventoryWindow.php new file mode 100644 index 00000000000..e9bdf8f7439 --- /dev/null +++ b/src/player/PlayerInventoryWindow.php @@ -0,0 +1,51 @@ +type; } +} diff --git a/src/player/SurvivalBlockBreakHandler.php b/src/player/SurvivalBlockBreakHandler.php index e31e77ef7c7..95db3916871 100644 --- a/src/player/SurvivalBlockBreakHandler.php +++ b/src/player/SurvivalBlockBreakHandler.php @@ -66,7 +66,7 @@ private function calculateBreakProgressPerTick() : float{ return 0.0; } //TODO: improve this to take stuff like swimming, ladders, enchanted tools into account, fix wrong tool break time calculations for bad tools (pmmp/PocketMine-MP#211) - $breakTimePerTick = $this->block->getBreakInfo()->getBreakTime($this->player->getInventory()->getItemInHand()) * 20; + $breakTimePerTick = $this->block->getBreakInfo()->getBreakTime($this->player->getHotbar()->getHeldItem()) * 20; if($breakTimePerTick > 0){ return 1 / $breakTimePerTick; 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 26a53b1718c..c4238656415 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{ }