*/ protected array $canPlaceOn = []; /** * @var string[] * @phpstan-var array */ protected array $canDestroy = []; /** * Constructs a new Item type. This constructor should ONLY be used when constructing a new item TYPE to register * into the index. * * NOTE: This should NOT BE USED for creating items to set into an inventory. Use VanillaItems for that * purpose. * @see VanillaItems */ public function __construct( private ItemIdentifier $identifier, protected string $name = "Unknown" ){ $this->nbt = new CompoundTag(); } public function hasCustomBlockData() : bool{ return $this->blockEntityTag !== null; } /** * @return $this */ public function clearCustomBlockData(){ $this->blockEntityTag = null; return $this; } /** * @return $this */ public function setCustomBlockData(CompoundTag $compound) : Item{ $this->blockEntityTag = clone $compound; return $this; } public function getCustomBlockData() : ?CompoundTag{ return $this->blockEntityTag; } public function hasCustomName() : bool{ return $this->customName !== ""; } public function getCustomName() : string{ return $this->customName; } /** * @return $this */ public function setCustomName(string $name) : Item{ Utils::checkUTF8($name); $this->customName = $name; return $this; } /** * @return $this */ public function clearCustomName() : Item{ $this->setCustomName(""); return $this; } /** * @return string[] */ public function getLore() : array{ return $this->lore; } /** * @param string[] $lines * * @return $this */ public function setLore(array $lines) : Item{ foreach($lines as $line){ if(!is_string($line)){ throw new \TypeError("Expected string[], but found " . gettype($line) . " in given array"); } Utils::checkUTF8($line); } $this->lore = $lines; return $this; } /** * @return string[] * @phpstan-return array */ public function getCanPlaceOn() : array{ return $this->canPlaceOn; } /** * @param string[] $canPlaceOn */ public function setCanPlaceOn(array $canPlaceOn) : void{ $this->canPlaceOn = []; foreach($canPlaceOn as $value){ $this->canPlaceOn[$value] = $value; } } /** * @return string[] * @phpstan-return array */ public function getCanDestroy() : array{ return $this->canDestroy; } /** * @param string[] $canDestroy */ public function setCanDestroy(array $canDestroy) : void{ $this->canDestroy = []; foreach($canDestroy as $value){ $this->canDestroy[$value] = $value; } } /** * Returns whether this Item has a non-empty NBT. */ public function hasNamedTag() : bool{ return $this->getNamedTag()->count() > 0; } /** * Returns a tree of Tag objects representing the Item's NBT. If the item does not have any NBT, an empty CompoundTag * object is returned to allow the caller to manipulate and apply back to the item. */ public function getNamedTag() : CompoundTag{ $this->serializeCompoundTag($this->nbt); return $this->nbt; } /** * Sets the Item's NBT from the supplied CompoundTag object. * * @return $this * @throws NbtException */ public function setNamedTag(CompoundTag $tag) : Item{ if($tag->getCount() === 0){ return $this->clearNamedTag(); } $this->nbt = clone $tag; $this->deserializeCompoundTag($this->nbt); return $this; } /** * Removes the Item's NBT. * @return $this * @throws NbtException */ public function clearNamedTag() : Item{ $this->nbt = new CompoundTag(); $this->deserializeCompoundTag($this->nbt); return $this; } /** * @throws NbtException */ protected function deserializeCompoundTag(CompoundTag $tag) : void{ $this->customName = ""; $this->lore = []; $display = $tag->getCompoundTag(self::TAG_DISPLAY); if($display !== null){ $this->customName = $display->getString(self::TAG_DISPLAY_NAME, $this->customName); $lore = $display->getListTag(self::TAG_DISPLAY_LORE); if($lore !== null && $lore->getTagType() === NBT::TAG_String){ /** @var StringTag $t */ foreach($lore as $t){ $this->lore[] = $t->getValue(); } } } $this->removeEnchantments(); $enchantments = $tag->getListTag(self::TAG_ENCH); if($enchantments !== null && $enchantments->getTagType() === NBT::TAG_Compound){ /** @var CompoundTag $enchantment */ foreach($enchantments as $enchantment){ $magicNumber = $enchantment->getShort("id", -1); $level = $enchantment->getShort("lvl", 0); if($level <= 0){ continue; } $type = EnchantmentIdMap::getInstance()->fromId($magicNumber); if($type !== null){ $this->addEnchantment(new EnchantmentInstance($type, $level)); } } } $this->blockEntityTag = $tag->getCompoundTag(self::TAG_BLOCK_ENTITY_TAG); $this->canPlaceOn = []; $canPlaceOn = $tag->getListTag("CanPlaceOn"); if($canPlaceOn !== null && $canPlaceOn->getTagType() === NBT::TAG_String){ /** @var StringTag $entry */ foreach($canPlaceOn as $entry){ $this->canPlaceOn[$entry->getValue()] = $entry->getValue(); } } $this->canDestroy = []; $canDestroy = $tag->getListTag("CanDestroy"); if($canDestroy !== null && $canDestroy->getTagType() === NBT::TAG_String){ /** @var StringTag $entry */ foreach($canDestroy as $entry){ $this->canDestroy[$entry->getValue()] = $entry->getValue(); } } } protected function serializeCompoundTag(CompoundTag $tag) : void{ $display = $tag->getCompoundTag(self::TAG_DISPLAY) ?? new CompoundTag(); $this->hasCustomName() ? $display->setString(self::TAG_DISPLAY_NAME, $this->getCustomName()) : $display->removeTag(self::TAG_DISPLAY_NAME); if(count($this->lore) > 0){ $loreTag = new ListTag(); foreach($this->lore as $line){ $loreTag->push(new StringTag($line)); } $display->setTag(self::TAG_DISPLAY_LORE, $loreTag); }else{ $display->removeTag(self::TAG_DISPLAY_LORE); } $display->count() > 0 ? $tag->setTag(self::TAG_DISPLAY, $display) : $tag->removeTag(self::TAG_DISPLAY); if($this->hasEnchantments()){ $ench = new ListTag(); foreach($this->getEnchantments() as $enchantmentInstance){ $ench->push(CompoundTag::create() ->setShort("id", EnchantmentIdMap::getInstance()->toId($enchantmentInstance->getType())) ->setShort("lvl", $enchantmentInstance->getLevel()) ); } $tag->setTag(self::TAG_ENCH, $ench); }else{ $tag->removeTag(self::TAG_ENCH); } ($blockData = $this->getCustomBlockData()) !== null ? $tag->setTag(self::TAG_BLOCK_ENTITY_TAG, clone $blockData) : $tag->removeTag(self::TAG_BLOCK_ENTITY_TAG); if(count($this->canPlaceOn) > 0){ $canPlaceOn = new ListTag(); foreach($this->canPlaceOn as $item){ $canPlaceOn->push(new StringTag($item)); } $tag->setTag("CanPlaceOn", $canPlaceOn); }else{ $tag->removeTag("CanPlaceOn"); } if(count($this->canDestroy) > 0){ $canDestroy = new ListTag(); foreach($this->canDestroy as $item){ $canDestroy->push(new StringTag($item)); } $tag->setTag("CanDestroy", $canDestroy); }else{ $tag->removeTag("CanDestroy"); } } public function getCount() : int{ return $this->count; } /** * @return $this */ public function setCount(int $count) : Item{ $this->count = $count; return $this; } /** * Pops an item from the stack and returns it, decreasing the stack count of this item stack by one. * * @return static A clone of this itemstack containing the amount of items that were removed from this stack. * @throws \InvalidArgumentException if trying to pop more items than are on the stack */ public function pop(int $count = 1) : Item{ if($count > $this->count){ throw new \InvalidArgumentException("Cannot pop $count items from a stack of $this->count"); } $item = clone $this; $item->count = $count; $this->count -= $count; return $item; } public function isNull() : bool{ return $this->count <= 0; } /** * Returns the name of the item, or the custom name if it is set. */ final public function getName() : string{ return $this->hasCustomName() ? $this->getCustomName() : $this->getVanillaName(); } /** * Returns the vanilla name of the item, disregarding custom names. */ public function getVanillaName() : string{ return $this->name; } final public function canBePlaced() : bool{ return $this->getBlock()->canBePlaced(); } /** * Returns the block corresponding to this Item. */ public function getBlock(?int $clickedFace = null) : Block{ return VanillaBlocks::AIR(); } final public function getTypeId() : int{ return $this->identifier->getTypeId(); } final public function computeTypeData() : int{ $writer = new RuntimeDataWriter(16); //TODO: max bits should be a constant instead of being hardcoded all over the place $this->encodeType($writer); return $writer->getValue(); } protected function encodeType(RuntimeDataWriter $w) : void{ //NOOP } /** * Returns the highest amount of this item which will fit into one inventory slot. */ public function getMaxStackSize() : int{ return 64; } /** * Returns the time in ticks which the item will fuel a furnace for. */ public function getFuelTime() : int{ return 0; } /** * Returns an item after burning fuel */ public function getFuelResidue() : Item{ $item = clone $this; $item->pop(); return $item; } /** * Returns how many points of damage this item will deal to an entity when used as a weapon. */ public function getAttackPoints() : int{ return 1; } /** * Returns how many armor points can be gained by wearing this item. */ public function getDefensePoints() : int{ return 0; } /** * Returns what type of block-breaking tool this is. Blocks requiring the same tool type as the item will break * faster (except for blocks requiring no tool, which break at the same speed regardless of the tool used) */ public function getBlockToolType() : int{ return BlockToolType::NONE; } /** * Returns the harvesting power that this tool has. This affects what blocks it can mine when the tool type matches * the mined block. * This should return 1 for non-tiered tools, and the tool tier for tiered tools. * * @see BlockBreakInfo::getToolHarvestLevel() */ public function getBlockToolHarvestLevel() : int{ return 0; } public function getMiningEfficiency(bool $isCorrectTool) : float{ return 1; } /** * Called when a player uses this item on a block. */ public function onInteractBlock(Player $player, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector) : ItemUseResult{ return ItemUseResult::NONE(); } /** * Called when a player uses the item on air, for example throwing a projectile. * Returns whether the item was changed, for example count decrease or durability change. */ public function onClickAir(Player $player, Vector3 $directionVector) : ItemUseResult{ return ItemUseResult::NONE(); } /** * Called when a player is using this item and releases it. Used to handle bow shoot actions. * Returns whether the item was changed, for example count decrease or durability change. */ public function onReleaseUsing(Player $player) : ItemUseResult{ return ItemUseResult::NONE(); } /** * Called when this item is used to destroy a block. Usually used to update durability. */ public function onDestroyBlock(Block $block) : bool{ return false; } /** * Called when this item is used to attack an entity. Usually used to update durability. */ public function onAttackEntity(Entity $victim) : bool{ return false; } /** * Returns the number of ticks a player must wait before activating this item again. */ public function getCooldownTicks() : int{ return 0; } /** * Compares an Item to this Item and check if they match. * * @param bool $checkDamage @deprecated * @param bool $checkCompound Whether to verify that the items' NBT match. */ final public function equals(Item $item, bool $checkDamage = true, bool $checkCompound = true) : bool{ return $this->getTypeId() === $item->getTypeId() && $this->computeTypeData() === $item->computeTypeData() && (!$checkCompound || $this->getNamedTag()->equals($item->getNamedTag())); } /** * Returns whether this item could stack with the given item (ignoring stack size and count). */ final public function canStackWith(Item $other) : bool{ return $this->equals($other, true, true); } /** * Returns whether the specified item stack has the same ID, damage, NBT and count as this item stack. */ final public function equalsExact(Item $other) : bool{ return $this->equals($other, true, true) && $this->count === $other->count; } final public function __toString() : string{ return "Item " . $this->name . " (" . $this->getTypeId() . ":" . $this->computeTypeData() . ")x" . $this->count . ($this->hasNamedTag() ? " tags:0x" . base64_encode((new LittleEndianNbtSerializer())->write(new TreeRoot($this->getNamedTag()))) : ""); } /** * @phpstan-return never */ public function jsonSerialize() : array{ throw new \LogicException("json_encode()ing Item instances is no longer supported. Make your own method to convert the item to an array or stdClass."); } /** * Deserializes item JSON data produced by json_encode()ing Item instances in older versions of PocketMine-MP. * This method exists solely to allow upgrading old JSON data stored by plugins. * * @param mixed[] $data * * @throws SavedDataLoadingException */ final public static function legacyJsonDeserialize(array $data) : Item{ $nbt = ""; //Backwards compatibility if(isset($data["nbt"])){ $nbt = $data["nbt"]; }elseif(isset($data["nbt_hex"])){ $nbt = hex2bin($data["nbt_hex"]); }elseif(isset($data["nbt_b64"])){ $nbt = base64_decode($data["nbt_b64"], true); } $itemStackData = GlobalItemDataHandlers::getUpgrader()->upgradeItemTypeDataInt( (int) $data["id"], (int) ($data["damage"] ?? 0), (int) ($data["count"] ?? 1), $nbt !== "" ? (new LittleEndianNbtSerializer())->read($nbt)->mustGetCompoundTag() : null ); try{ return GlobalItemDataHandlers::getDeserializer()->deserializeStack($itemStackData); }catch(ItemTypeDeserializeException $e){ throw new SavedDataLoadingException($e->getMessage(), 0, $e); } } /** * Serializes the item to an NBT CompoundTag * * @param int $slot optional, the inventory slot of the item */ public function nbtSerialize(int $slot = -1) : CompoundTag{ return GlobalItemDataHandlers::getSerializer()->serializeStack($this, $slot !== -1 ? $slot : null)->toNbt(); } /** * Deserializes an Item from an NBT CompoundTag * @throws SavedDataLoadingException */ public static function nbtDeserialize(CompoundTag $tag) : Item{ $itemData = GlobalItemDataHandlers::getUpgrader()->upgradeItemStackNbt($tag); if($itemData === null){ return VanillaItems::AIR(); } try{ return GlobalItemDataHandlers::getDeserializer()->deserializeStack($itemData); }catch(ItemTypeDeserializeException $e){ throw new SavedDataLoadingException($e->getMessage(), 0, $e); } } public function __clone(){ $this->nbt = clone $this->nbt; if($this->blockEntityTag !== null){ $this->blockEntityTag = clone $this->blockEntityTag; } } }