diff --git a/src/block/Barrel.php b/src/block/Barrel.php index 2b781a17d..1b64e814c 100644 --- a/src/block/Barrel.php +++ b/src/block/Barrel.php @@ -23,8 +23,10 @@ declare(strict_types=1); namespace pocketmine\block; -use pocketmine\block\inventory\window\BarrelInventoryWindow; +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; @@ -32,9 +34,14 @@ use pocketmine\math\Facing; 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; @@ -82,7 +89,7 @@ class Barrel extends Opaque{ return true; } - $player->setCurrentWindow(new BarrelInventoryWindow($player, $barrel->getInventory(), $this->position)); + $player->setCurrentWindow(new BlockInventoryWindow($player, $barrel->getInventory(), $this->position)); } } @@ -92,4 +99,20 @@ class Barrel extends Opaque{ 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/Chest.php b/src/block/Chest.php index 539626535..a688df0bd 100644 --- a/src/block/Chest.php +++ b/src/block/Chest.php @@ -24,9 +24,11 @@ declare(strict_types=1); namespace pocketmine\block; use pocketmine\block\inventory\DoubleChestInventory; -use pocketmine\block\inventory\window\ChestInventoryWindow; +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; @@ -34,9 +36,17 @@ 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\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; /** @@ -51,6 +61,25 @@ class Chest extends Transparent{ 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); @@ -90,27 +119,23 @@ class Chest extends Transparent{ $window = null; if($chest->isPaired()){ - foreach([false, true] as $clockwise){ - $sideFacing = Facing::rotateY($this->facing, $clockwise); - $side = $this->position->getSide($sideFacing); - $pair = $world->getTile($side); - if($pair instanceof TileChest && $pair->getPair() === $chest){ - [$left, $right] = $clockwise ? [$pair, $chest] : [$chest, $pair]; + $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()); - break; + $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 ChestInventoryWindow($player, $chest->getInventory(), $this->position)); + $player->setCurrentWindow($window ?? new BlockInventoryWindow($player, $chest->getInventory(), $this->position)); } } @@ -120,4 +145,39 @@ class Chest extends Transparent{ 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/EnderChest.php b/src/block/EnderChest.php index 675bdef58..c5901d3f4 100644 --- a/src/block/EnderChest.php +++ b/src/block/EnderChest.php @@ -23,17 +23,29 @@ declare(strict_types=1); namespace pocketmine\block; -use pocketmine\block\inventory\window\EnderChestInventoryWindow; +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,7 +68,7 @@ class EnderChest extends Transparent{ if($player instanceof Player){ $enderChest = $this->position->getWorld()->getTile($this->position); if($enderChest instanceof TileEnderChest && $this->getSide(Facing::UP)->isTransparent()){ - $player->setCurrentWindow(new EnderChestInventoryWindow($player, $player->getEnderInventory(), $this->position)); + $player->setCurrentWindow(new BlockInventoryWindow($player, $player->getEnderInventory(), $this->position)); } } @@ -72,4 +84,43 @@ class EnderChest extends Transparent{ 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/ShulkerBox.php b/src/block/ShulkerBox.php index a21a373b6..170033963 100644 --- a/src/block/ShulkerBox.php +++ b/src/block/ShulkerBox.php @@ -23,17 +23,26 @@ declare(strict_types=1); namespace pocketmine\block; -use pocketmine\block\inventory\window\ShulkerBoxInventoryWindow; +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{ @@ -106,7 +115,7 @@ class ShulkerBox extends Opaque{ return true; } - $player->setCurrentWindow(new ShulkerBoxInventoryWindow($player, $shulker->getInventory(), $this->position)); + $player->setCurrentWindow(new BlockInventoryWindow($player, $shulker->getInventory(), $this->position)); } } @@ -116,4 +125,18 @@ class ShulkerBox extends Opaque{ 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/inventory/window/AnimatedBlockInventoryWindow.php b/src/block/inventory/window/AnimatedBlockInventoryWindow.php deleted file mode 100644 index 29c700298..000000000 --- a/src/block/inventory/window/AnimatedBlockInventoryWindow.php +++ /dev/null @@ -1,65 +0,0 @@ -inventory->getViewers()); - } - - abstract protected function getOpenSound() : Sound; - - abstract protected function getCloseSound() : Sound; - - abstract protected function animateBlock(Position $position, bool $isOpen) : void; - - protected function playSound(Position $position, bool $isOpen) : void{ - $position->getWorld()->addSound($position->add(0.5, 0.5, 0.5), $isOpen ? $this->getOpenSound() : $this->getCloseSound()); - } - - protected function doBlockEffects(bool $isOpen) : void{ - $position = $this->holder; - $this->animateBlock($position, $isOpen); - $this->playSound($position, $isOpen); - } - - public function onOpen() : void{ - parent::onOpen(); - if($this->getViewerCount() === 1){ - $this->doBlockEffects(true); - } - } - - public function onClose() : void{ - if($this->getViewerCount() === 1){ - $this->doBlockEffects(false); - } - parent::onClose(); - } -} diff --git a/src/block/inventory/window/BarrelInventoryWindow.php b/src/block/inventory/window/BarrelInventoryWindow.php deleted file mode 100644 index 7dd4d0dde..000000000 --- a/src/block/inventory/window/BarrelInventoryWindow.php +++ /dev/null @@ -1,49 +0,0 @@ -getWorld(); - $block = $world->getBlock($position); - if($block instanceof Barrel){ - $world->setBlock($position, $block->setOpen($isOpen)); - } - } -} diff --git a/src/block/inventory/window/BlockInventoryWindow.php b/src/block/inventory/window/BlockInventoryWindow.php index b2a84a851..149c2e814 100644 --- a/src/block/inventory/window/BlockInventoryWindow.php +++ b/src/block/inventory/window/BlockInventoryWindow.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\block\inventory\window; +use pocketmine\block\utils\AnimatedContainer; use pocketmine\inventory\Inventory; use pocketmine\player\InventoryWindow; use pocketmine\player\Player; @@ -39,4 +40,20 @@ class BlockInventoryWindow extends InventoryWindow{ } public function getHolder() : Position{ return $this->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/window/ChestInventoryWindow.php b/src/block/inventory/window/ChestInventoryWindow.php deleted file mode 100644 index 6b3dda978..000000000 --- a/src/block/inventory/window/ChestInventoryWindow.php +++ /dev/null @@ -1,48 +0,0 @@ -getWorld()->broadcastPacketToViewers($position, BlockEventPacket::create(BlockPosition::fromVector3($position), 1, $isOpen ? 1 : 0)); - } -} diff --git a/src/block/inventory/window/DoubleChestInventoryWindow.php b/src/block/inventory/window/DoubleChestInventoryWindow.php index 5455b1f51..801a6ff9d 100644 --- a/src/block/inventory/window/DoubleChestInventoryWindow.php +++ b/src/block/inventory/window/DoubleChestInventoryWindow.php @@ -27,7 +27,7 @@ use pocketmine\inventory\Inventory; use pocketmine\player\Player; use pocketmine\world\Position; -final class DoubleChestInventoryWindow extends ChestInventoryWindow{ +final class DoubleChestInventoryWindow extends BlockInventoryWindow{ public function __construct( Player $viewer, @@ -41,12 +41,4 @@ final class DoubleChestInventoryWindow extends ChestInventoryWindow{ public function getLeft() : Position{ return $this->left; } public function getRight() : Position{ return $this->right; } - - protected function doBlockEffects(bool $isOpen) : void{ - $this->animateBlock($this->left, $isOpen); - $this->animateBlock($this->right, $isOpen); - - $this->playSound($this->left, $isOpen); - $this->playSound($this->right, $isOpen); - } } diff --git a/src/block/inventory/window/EnderChestInventoryWindow.php b/src/block/inventory/window/EnderChestInventoryWindow.php deleted file mode 100644 index 97d951c93..000000000 --- a/src/block/inventory/window/EnderChestInventoryWindow.php +++ /dev/null @@ -1,73 +0,0 @@ -holder->getWorld()->getTile($this->getHolder()); - if(!$enderChest instanceof EnderChest){ - return 0; - } - return $enderChest->getViewerCount(); - } - - private function updateViewerCount(int $amount) : void{ - $enderChest = $this->holder->getWorld()->getTile($this->getHolder()); - if($enderChest instanceof EnderChest){ - $enderChest->setViewerCount($enderChest->getViewerCount() + $amount); - } - } - - protected function getOpenSound() : Sound{ - return new EnderChestOpenSound(); - } - - protected function getCloseSound() : Sound{ - return new EnderChestCloseSound(); - } - - protected function animateBlock(Position $position, bool $isOpen) : void{ - //event ID is always 1 for a chest - $position->getWorld()->broadcastPacketToViewers($position, BlockEventPacket::create(BlockPosition::fromVector3($position), 1, $isOpen ? 1 : 0)); - } - - public function onOpen() : void{ - parent::onOpen(); - $this->updateViewerCount(1); - } - - public function onClose() : void{ - parent::onClose(); - $this->updateViewerCount(-1); - } -} diff --git a/src/block/inventory/window/ShulkerBoxInventoryWindow.php b/src/block/inventory/window/ShulkerBoxInventoryWindow.php deleted file mode 100644 index 19d1cba18..000000000 --- a/src/block/inventory/window/ShulkerBoxInventoryWindow.php +++ /dev/null @@ -1,46 +0,0 @@ -getWorld()->broadcastPacketToViewers($position, BlockEventPacket::create(BlockPosition::fromVector3($position), 1, $isOpen ? 1 : 0)); - } -} diff --git a/src/player/Player.php b/src/player/Player.php index 3b54a2b86..e87fb662d 100644 --- a/src/player/Player.php +++ b/src/player/Player.php @@ -2693,6 +2693,9 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ if($window === $this->currentWindow){ return true; } + 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()){