Integrate block container animations and SFX into Block classes by way of AnimatedContainer interface

this allows getting rid of several container window classes.

we should probably look into adding more info to the BlockInventoryWindow to make the type easier to identify, though.
now that holder is tracked by an ephemeral window, we can put whatever we like in there.
This commit is contained in:
Dylan K. Taylor 2024-11-25 10:55:35 +00:00
parent f4d50a1aa1
commit 4dcc14e0a1
No known key found for this signature in database
GPG Key ID: 8927471A91CAFD3D
12 changed files with 205 additions and 317 deletions

View File

@ -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));
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -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));
}
}

View File

@ -1,65 +0,0 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\block\inventory\window;
use pocketmine\world\Position;
use pocketmine\world\sound\Sound;
use function count;
abstract class AnimatedBlockInventoryWindow extends BlockInventoryWindow{
protected function getViewerCount() : int{
return count($this->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();
}
}

View File

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

View File

@ -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();
}
}

View File

@ -1,48 +0,0 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\block\inventory\window;
use pocketmine\network\mcpe\protocol\BlockEventPacket;
use pocketmine\network\mcpe\protocol\types\BlockPosition;
use pocketmine\world\Position;
use pocketmine\world\sound\ChestCloseSound;
use pocketmine\world\sound\ChestOpenSound;
use pocketmine\world\sound\Sound;
class ChestInventoryWindow extends AnimatedBlockInventoryWindow{
protected function getOpenSound() : Sound{
return new ChestOpenSound();
}
protected function getCloseSound() : Sound{
return new ChestCloseSound();
}
protected function animateBlock(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));
}
}

View File

@ -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);
}
}

View File

@ -1,73 +0,0 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\block\inventory\window;
use pocketmine\block\tile\EnderChest;
use pocketmine\network\mcpe\protocol\BlockEventPacket;
use pocketmine\network\mcpe\protocol\types\BlockPosition;
use pocketmine\world\Position;
use pocketmine\world\sound\EnderChestCloseSound;
use pocketmine\world\sound\EnderChestOpenSound;
use pocketmine\world\sound\Sound;
final class EnderChestInventoryWindow extends AnimatedBlockInventoryWindow{
protected function getViewerCount() : int{
$enderChest = $this->holder->getWorld()->getTile($this->getHolder());
if(!$enderChest instanceof EnderChest){
return 0;
}
return $enderChest->getViewerCount();
}
private function updateViewerCount(int $amount) : void{
$enderChest = $this->holder->getWorld()->getTile($this->getHolder());
if($enderChest instanceof EnderChest){
$enderChest->setViewerCount($enderChest->getViewerCount() + $amount);
}
}
protected function getOpenSound() : Sound{
return new EnderChestOpenSound();
}
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);
}
}

View File

@ -1,46 +0,0 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\block\inventory\window;
use pocketmine\network\mcpe\protocol\BlockEventPacket;
use pocketmine\network\mcpe\protocol\types\BlockPosition;
use pocketmine\world\Position;
use pocketmine\world\sound\ShulkerBoxCloseSound;
use pocketmine\world\sound\ShulkerBoxOpenSound;
use pocketmine\world\sound\Sound;
final class ShulkerBoxInventoryWindow extends AnimatedBlockInventoryWindow{
protected function getOpenSound() : Sound{
return new ShulkerBoxOpenSound();
}
protected function getCloseSound() : Sound{
return new ShulkerBoxCloseSound();
}
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));
}
}

View File

@ -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()){