diff --git a/src/block/Barrel.php b/src/block/Barrel.php index 0f0499ab93..7d6a7e924e 100644 --- a/src/block/Barrel.php +++ b/src/block/Barrel.php @@ -25,6 +25,7 @@ use pocketmine\block\tile\Barrel as TileBarrel; use pocketmine\block\utils\AnyFacingTrait; +use pocketmine\block\utils\ContainerTrait; use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\item\Item; use pocketmine\math\Facing; @@ -35,6 +36,7 @@ class Barrel extends Opaque{ use AnyFacingTrait; + use ContainerTrait; protected bool $open = false; diff --git a/src/block/BrewingStand.php b/src/block/BrewingStand.php index 0396931649..b8f605afc5 100644 --- a/src/block/BrewingStand.php +++ b/src/block/BrewingStand.php @@ -25,6 +25,7 @@ use pocketmine\block\tile\BrewingStand as TileBrewingStand; use pocketmine\block\utils\BrewingStandSlot; +use pocketmine\block\utils\ContainerTrait; use pocketmine\block\utils\SupportType; use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\item\Item; @@ -37,6 +38,7 @@ use function spl_object_id; class BrewingStand extends Transparent{ + use ContainerTrait; /** * @var BrewingStandSlot[] diff --git a/src/block/Chest.php b/src/block/Chest.php index dca21576aa..c85b99bfcd 100644 --- a/src/block/Chest.php +++ b/src/block/Chest.php @@ -24,6 +24,7 @@ namespace pocketmine\block; use pocketmine\block\tile\Chest as TileChest; +use pocketmine\block\utils\ContainerTrait; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; use pocketmine\block\utils\SupportType; use pocketmine\event\block\ChestPairEvent; @@ -35,6 +36,7 @@ class Chest extends Transparent{ use FacesOppositePlacingPlayerTrait; + use ContainerTrait; /** * @return AxisAlignedBB[] diff --git a/src/block/ChiseledBookshelf.php b/src/block/ChiseledBookshelf.php index 73c4861bf3..ab6d161d84 100644 --- a/src/block/ChiseledBookshelf.php +++ b/src/block/ChiseledBookshelf.php @@ -25,6 +25,7 @@ use pocketmine\block\tile\ChiseledBookshelf as TileChiseledBookshelf; use pocketmine\block\utils\ChiseledBookshelfSlot; +use pocketmine\block\utils\ContainerTrait; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; use pocketmine\block\utils\HorizontalFacingTrait; use pocketmine\data\runtime\RuntimeDataDescriber; @@ -41,6 +42,7 @@ class ChiseledBookshelf extends Opaque{ use HorizontalFacingTrait; use FacesOppositePlacingPlayerTrait; + use ContainerTrait; /** * @var ChiseledBookshelfSlot[] diff --git a/src/block/Furnace.php b/src/block/Furnace.php index 7a64e3cd3e..ce488f3d29 100644 --- a/src/block/Furnace.php +++ b/src/block/Furnace.php @@ -24,6 +24,7 @@ namespace pocketmine\block; use pocketmine\block\tile\Furnace as TileFurnace; +use pocketmine\block\utils\ContainerTrait; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; use pocketmine\block\utils\LightableTrait; use pocketmine\crafting\FurnaceType; @@ -36,6 +37,7 @@ class Furnace extends Opaque{ use FacesOppositePlacingPlayerTrait; use LightableTrait; + use ContainerTrait; protected FurnaceType $furnaceType; diff --git a/src/block/Hopper.php b/src/block/Hopper.php index 0d823674b2..90848bf61b 100644 --- a/src/block/Hopper.php +++ b/src/block/Hopper.php @@ -24,6 +24,7 @@ namespace pocketmine\block; use pocketmine\block\tile\Hopper as TileHopper; +use pocketmine\block\utils\ContainerTrait; use pocketmine\block\utils\PoweredByRedstoneTrait; use pocketmine\block\utils\SupportType; use pocketmine\data\runtime\RuntimeDataDescriber; @@ -36,6 +37,7 @@ class Hopper extends Transparent{ use PoweredByRedstoneTrait; + use ContainerTrait; private int $facing = Facing::DOWN; diff --git a/src/block/ShulkerBox.php b/src/block/ShulkerBox.php index d557401eec..f20b3575a6 100644 --- a/src/block/ShulkerBox.php +++ b/src/block/ShulkerBox.php @@ -68,10 +68,7 @@ public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Blo } private function addDataFromTile(TileShulkerBox $tile, Item $item) : void{ - $shulkerNBT = $tile->getCleanedNBT(); - if($shulkerNBT !== null){ - $item->setNamedTag($shulkerNBT); - } + $item->setContainedItems($tile->getRealInventory()->getContents()); if($tile->hasName()){ $item->setCustomName($tile->getName()); } diff --git a/src/block/tile/Barrel.php b/src/block/tile/Barrel.php index a7f3532142..c2ce16bd01 100644 --- a/src/block/tile/Barrel.php +++ b/src/block/tile/Barrel.php @@ -24,12 +24,15 @@ namespace pocketmine\block\tile; use pocketmine\block\inventory\BarrelInventory; +use pocketmine\item\Item; use pocketmine\math\Vector3; use pocketmine\nbt\tag\CompoundTag; use pocketmine\world\World; class Barrel extends Spawnable implements Container, Nameable{ - use NameableTrait; + use NameableTrait { + copyDataFromItem as copyNameFromItem; + } use ContainerTrait; protected BarrelInventory $inventory; @@ -49,6 +52,11 @@ protected function writeSaveData(CompoundTag $nbt) : void{ $this->saveItems($nbt); } + public function copyDataFromItem(Item $item) : void{ + $this->copyNameFromItem($item); + $this->copyContentsFromItem($item); + } + public function close() : void{ if(!$this->closed){ $this->inventory->removeAllViewers(); diff --git a/src/block/tile/BrewingStand.php b/src/block/tile/BrewingStand.php index c3a331e6c6..ac93299a32 100644 --- a/src/block/tile/BrewingStand.php +++ b/src/block/tile/BrewingStand.php @@ -43,6 +43,7 @@ class BrewingStand extends Spawnable implements Container, Nameable{ use NameableTrait { addAdditionalSpawnData as addNameSpawnData; + copyDataFromItem as copyNameFromItem; } use ContainerTrait; @@ -92,6 +93,11 @@ protected function writeSaveData(CompoundTag $nbt) : void{ $nbt->setShort(self::TAG_REMAINING_FUEL_TIME_PE, $this->remainingFuelTime); } + public function copyDataFromItem(Item $item) : void{ + $this->copyNameFromItem($item); + $this->copyContentsFromItem($item); + } + protected function addAdditionalSpawnData(CompoundTag $nbt) : void{ $this->addNameSpawnData($nbt); diff --git a/src/block/tile/Chest.php b/src/block/tile/Chest.php index 4f97eed234..8fe9fb373a 100644 --- a/src/block/tile/Chest.php +++ b/src/block/tile/Chest.php @@ -25,6 +25,7 @@ use pocketmine\block\inventory\ChestInventory; use pocketmine\block\inventory\DoubleChestInventory; +use pocketmine\item\Item; use pocketmine\math\Vector3; use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\IntTag; @@ -35,8 +36,10 @@ class Chest extends Spawnable implements Container, Nameable{ use NameableTrait { addAdditionalSpawnData as addNameSpawnData; + copyDataFromItem as copyNameFromItem; } use ContainerTrait { + getCleanedNBT as getContainerNBT; onBlockDestroyedHook as containerTraitBlockDestroyedHook; } @@ -83,7 +86,7 @@ protected function writeSaveData(CompoundTag $nbt) : void{ } public function getCleanedNBT() : ?CompoundTag{ - $tag = parent::getCleanedNBT(); + $tag = $this->getContainerNBT(); if($tag !== null){ //TODO: replace this with a purpose flag on writeSaveData() $tag->removeTag(self::TAG_PAIRX, self::TAG_PAIRZ); @@ -91,6 +94,11 @@ public function getCleanedNBT() : ?CompoundTag{ return $tag; } + public function copyDataFromItem(Item $item) : void{ + $this->copyNameFromItem($item); + $this->copyContentsFromItem($item); + } + public function close() : void{ if(!$this->closed){ $this->inventory->removeAllViewers(); diff --git a/src/block/tile/ChiseledBookshelf.php b/src/block/tile/ChiseledBookshelf.php index 06175e27f4..06f687a267 100644 --- a/src/block/tile/ChiseledBookshelf.php +++ b/src/block/tile/ChiseledBookshelf.php @@ -136,4 +136,9 @@ protected function saveItems(CompoundTag $tag) : void{ $tag->setString(Container::TAG_LOCK, $this->lock); } } + + public function copyDataFromItem(Item $item) : void{ + parent::copyDataFromItem($item); + $this->copyContentsFromItem($item); + } } diff --git a/src/block/tile/ContainerTrait.php b/src/block/tile/ContainerTrait.php index fdd050a416..bafb2a2bc1 100644 --- a/src/block/tile/ContainerTrait.php +++ b/src/block/tile/ContainerTrait.php @@ -23,13 +23,10 @@ namespace pocketmine\block\tile; -use pocketmine\data\bedrock\item\SavedItemStackData; -use pocketmine\data\SavedDataLoadingException; +use pocketmine\block\tile\utils\ContainerHelper; use pocketmine\inventory\Inventory; use pocketmine\item\Item; -use pocketmine\nbt\NBT; use pocketmine\nbt\tag\CompoundTag; -use pocketmine\nbt\tag\ListTag; use pocketmine\nbt\tag\StringTag; use pocketmine\world\Position; @@ -43,22 +40,12 @@ trait ContainerTrait{ 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){ + $newContents = ContainerHelper::deserializeContents($tag); + if($newContents !== null){ $inventory = $this->getRealInventory(); $listeners = $inventory->getListeners()->toArray(); $inventory->getListeners()->remove(...$listeners); //prevent any events being fired by initialization - $newContents = []; - /** @var CompoundTag $itemNBT */ - foreach($inventoryTag as $itemNBT){ - try{ - $newContents[$itemNBT->getByte(SavedItemStackData::TAG_SLOT)] = Item::nbtDeserialize($itemNBT); - }catch(SavedDataLoadingException $e){ - //TODO: not the best solution - \GlobalLogger::get()->logException($e); - continue; - } - } $inventory->setContents($newContents); $inventory->getListeners()->add(...$listeners); @@ -70,18 +57,37 @@ protected function loadItems(CompoundTag $tag) : void{ } protected function saveItems(CompoundTag $tag) : void{ - $items = []; - foreach($this->getRealInventory()->getContents() as $slot => $item){ - $items[] = $item->nbtSerialize($slot); - } - - $tag->setTag(Container::TAG_ITEMS, new ListTag($items, NBT::TAG_Compound)); + ContainerHelper::serializeContents($tag, $this->getRealInventory()->getContents()); if($this->lock !== null){ $tag->setString(Container::TAG_LOCK, $this->lock); } } + /** + * @see Tile::getCleanedNBT() + */ + public function getCleanedNBT() : ?CompoundTag{ + $tag = parent::getCleanedNBT(); + if($tag !== null){ + $tag->removeTag(Container::TAG_ITEMS); + } + return $tag; + } + + /** + * @see Tile::copyDataFromItem() + */ + public function copyContentsFromItem(Item $item) : void{ + $inventory = $this->getRealInventory(); + $listeners = $inventory->getListeners()->toArray(); + $inventory->getListeners()->remove(...$listeners); //prevent any events being fired by initialization + + $inventory->setContents($item->getContainedItems()); + + $inventory->getListeners()->add(...$listeners); + } + /** * @see Container::canOpenWith() */ diff --git a/src/block/tile/Furnace.php b/src/block/tile/Furnace.php index a706a827e7..c14278636f 100644 --- a/src/block/tile/Furnace.php +++ b/src/block/tile/Furnace.php @@ -41,7 +41,9 @@ use function max; abstract class Furnace extends Spawnable implements Container, Nameable{ - use NameableTrait; + use NameableTrait { + copyDataFromItem as copyNameFromItem; + } use ContainerTrait; public const TAG_BURN_TIME = "BurnTime"; @@ -84,6 +86,11 @@ public function readSaveData(CompoundTag $nbt) : void{ } } + public function copyDataFromItem(Item $item) : void{ + $this->copyNameFromItem($item); + $this->copyContentsFromItem($item); + } + protected function writeSaveData(CompoundTag $nbt) : void{ $nbt->setShort(self::TAG_BURN_TIME, $this->remainingFuelTime); $nbt->setShort(self::TAG_COOK_TIME, $this->cookTime); diff --git a/src/block/tile/Hopper.php b/src/block/tile/Hopper.php index 5c39bc2bd5..fc495df9bc 100644 --- a/src/block/tile/Hopper.php +++ b/src/block/tile/Hopper.php @@ -24,14 +24,17 @@ namespace pocketmine\block\tile; use pocketmine\block\inventory\HopperInventory; +use pocketmine\item\Item; use pocketmine\math\Vector3; use pocketmine\nbt\tag\CompoundTag; use pocketmine\world\World; class Hopper extends Spawnable implements Container, Nameable{ + use NameableTrait { + copyDataFromItem as copyNameFromItem; + } use ContainerTrait; - use NameableTrait; private const TAG_TRANSFER_COOLDOWN = "TransferCooldown"; @@ -57,6 +60,11 @@ protected function writeSaveData(CompoundTag $nbt) : void{ $nbt->setInt(self::TAG_TRANSFER_COOLDOWN, $this->transferCooldown); } + public function copyDataFromItem(Item $item) : void{ + $this->copyNameFromItem($item); + $this->copyContentsFromItem($item); + } + public function close() : void{ if(!$this->closed){ $this->inventory->removeAllViewers(); diff --git a/src/block/tile/ShulkerBox.php b/src/block/tile/ShulkerBox.php index a30b75c4ea..6bce60cbda 100644 --- a/src/block/tile/ShulkerBox.php +++ b/src/block/tile/ShulkerBox.php @@ -34,7 +34,9 @@ class ShulkerBox extends Spawnable implements Container, Nameable{ use NameableTrait { addAdditionalSpawnData as addNameSpawnData; } - use ContainerTrait; + use ContainerTrait { + getCleanedNBT as getContainerNBT; + } public const TAG_FACING = "facing"; @@ -60,7 +62,7 @@ protected function writeSaveData(CompoundTag $nbt) : void{ } public function copyDataFromItem(Item $item) : void{ - $this->readSaveData($item->getNamedTag()); + $this->copyContentsFromItem($item); if($item->hasCustomName()){ $this->setName($item->getCustomName()); } @@ -78,7 +80,7 @@ protected function onBlockDestroyedHook() : void{ } public function getCleanedNBT() : ?CompoundTag{ - $nbt = parent::getCleanedNBT(); + $nbt = $this->getContainerNBT(); if($nbt !== null){ $nbt->removeTag(self::TAG_FACING); } diff --git a/src/block/tile/utils/ContainerHelper.php b/src/block/tile/utils/ContainerHelper.php new file mode 100644 index 0000000000..1db862ddc2 --- /dev/null +++ b/src/block/tile/utils/ContainerHelper.php @@ -0,0 +1,76 @@ +|null + */ + public static function deserializeContents(CompoundTag $tag) : ?array{ + if(($inventoryTag = $tag->getTag(Container::TAG_ITEMS)) instanceof ListTag && $inventoryTag->getTagType() === NBT::TAG_Compound){ + $contents = []; + /** @var CompoundTag $itemNBT */ + foreach($inventoryTag as $itemNBT){ + try{ + $contents[$itemNBT->getByte(SavedItemStackData::TAG_SLOT)] = Item::nbtDeserialize($itemNBT); + }catch(SavedDataLoadingException $e){ + //TODO: not the best solution + \GlobalLogger::get()->logException($e); + continue; + } + } + + return $contents; + }else{ + return null; + } + } + + /** + * @param Item[] $contents + * @phpstan-param array $contents + */ + public static function serializeContents(CompoundTag $tag, array $contents) : void{ + $items = []; + foreach($contents as $slot => $item){ + $items[] = $item->nbtSerialize($slot); + } + + $tag->setTag(Container::TAG_ITEMS, new ListTag($items, NBT::TAG_Compound)); + } +} diff --git a/src/block/utils/ContainerTrait.php b/src/block/utils/ContainerTrait.php new file mode 100644 index 0000000000..45bf7c16bd --- /dev/null +++ b/src/block/utils/ContainerTrait.php @@ -0,0 +1,41 @@ +position->getWorld()->getTile($this->position); + if($tile instanceof Container){ + $item->setContainedItems($tile->getRealInventory()->getContents()); + } + } + return $item; + } +} diff --git a/src/entity/object/ItemEntity.php b/src/entity/object/ItemEntity.php index 90eeece67c..72886c1b47 100644 --- a/src/entity/object/ItemEntity.php +++ b/src/entity/object/ItemEntity.php @@ -350,4 +350,10 @@ public function onCollideWithPlayer(Player $player) : void{ } $this->flagForDespawn(); } + + protected function onDeath() : void{ + foreach($this->item->getContainedItems() as $item){ + $this->getWorld()->dropItem($this->location, $item); + } + } } diff --git a/src/item/Item.php b/src/item/Item.php index 205f15e130..b3f9e96628 100644 --- a/src/item/Item.php +++ b/src/item/Item.php @@ -29,6 +29,8 @@ use pocketmine\block\Block; use pocketmine\block\BlockBreakInfo; use pocketmine\block\BlockToolType; +use pocketmine\block\tile\Container; +use pocketmine\block\tile\utils\ContainerHelper; use pocketmine\block\VanillaBlocks; use pocketmine\data\bedrock\EnchantmentIdMap; use pocketmine\data\bedrock\item\ItemTypeDeserializeException; @@ -100,6 +102,12 @@ class Item implements \JsonSerializable{ protected bool $keepOnDeath = false; + /** + * @var Item[] + * @phpsptan-var array + */ + protected array $containedItems = []; + /** * Constructs a new Item type. This constructor should ONLY be used when constructing a new item TYPE to register * into the index. @@ -238,6 +246,23 @@ public function setKeepOnDeath(bool $keepOnDeath) : void{ $this->keepOnDeath = $keepOnDeath; } + /** + * @return Item[] + * @phpstan-return array + */ + public function getContainedItems() : array{ + return $this->containedItems; + } + + /** + * @param Item[] $items + * @phpstan-param array $items + */ + public function setContainedItems(array $items) : void{ + Utils::validateArrayValueType($items, static function(Item $_) : void{}); + $this->containedItems = Utils::cloneObjectArray($items); + } + /** * Returns whether this Item has a non-empty NBT. */ @@ -338,6 +363,9 @@ protected function deserializeCompoundTag(CompoundTag $tag) : void{ } $this->keepOnDeath = $tag->getByte(self::TAG_KEEP_ON_DEATH, 0) !== 0; + + $contents = ContainerHelper::deserializeContents($tag); + $this->containedItems = $contents !== null ? $contents : []; } protected function serializeCompoundTag(CompoundTag $tag) : void{ @@ -406,6 +434,12 @@ protected function serializeCompoundTag(CompoundTag $tag) : void{ }else{ $tag->removeTag(self::TAG_KEEP_ON_DEATH); } + + if(count($this->containedItems) > 0){ + ContainerHelper::serializeContents($tag, $this->containedItems); + }else{ + $tag->removeTag(Container::TAG_ITEMS); + } } public function getCount() : int{