22declare(strict_types=1);
52use
function base64_decode;
53use
function base64_encode;
57use
function is_string;
58use
function morton2d_encode;
60class Item implements \JsonSerializable{
63 public const TAG_ENCH =
"ench";
64 private const TAG_ENCH_ID =
"id";
65 private const TAG_ENCH_LVL =
"lvl";
67 public const TAG_DISPLAY =
"display";
68 public const TAG_BLOCK_ENTITY_TAG =
"BlockEntityTag";
70 public const TAG_DISPLAY_NAME =
"Name";
71 public const TAG_DISPLAY_LORE =
"Lore";
73 public const TAG_KEEP_ON_DEATH =
"minecraft:keep_on_death";
75 private const TAG_CAN_PLACE_ON =
"CanPlaceOn";
76 private const TAG_CAN_DESTROY =
"CanDestroy";
80 protected int $count = 1;
84 protected string $customName =
"";
86 protected array $lore = [];
94 protected array $canPlaceOn = [];
99 protected array $canDestroy = [];
101 protected bool $keepOnDeath =
false;
115 protected string $name =
"Unknown",
116 private array $enchantmentTags = []
121 public function hasCustomBlockData() : bool{
122 return $this->blockEntityTag !== null;
129 $this->blockEntityTag =
null;
137 $this->blockEntityTag = clone $compound;
142 public function getCustomBlockData() : ?
CompoundTag{
143 return $this->blockEntityTag;
146 public function hasCustomName() : bool{
147 return $this->customName !==
"";
150 public function getCustomName() : string{
151 return $this->customName;
158 Utils::checkUTF8($name);
159 $this->customName = $name;
167 $this->setCustomName(
"");
184 foreach($lines as $line){
185 if(!is_string($line)){
186 throw new \TypeError(
"Expected string[], but found " . gettype($line) .
" in given array");
188 Utils::checkUTF8($line);
190 $this->lore = $lines;
199 return $this->canPlaceOn;
206 $this->canPlaceOn = [];
207 foreach($canPlaceOn as $value){
208 $this->canPlaceOn[$value] = $value;
217 return $this->canDestroy;
224 $this->canDestroy = [];
225 foreach($canDestroy as $value){
226 $this->canDestroy[$value] = $value;
234 return $this->keepOnDeath;
237 public function setKeepOnDeath(
bool $keepOnDeath) : void{
238 $this->keepOnDeath = $keepOnDeath;
245 return $this->getNamedTag()->count() > 0;
253 $this->serializeCompoundTag($this->nbt);
264 if($tag->getCount() === 0){
265 return $this->clearNamedTag();
268 $this->nbt = clone $tag;
269 $this->deserializeCompoundTag($this->nbt);
281 $this->deserializeCompoundTag($this->nbt);
289 $this->customName =
"";
293 if($display !==
null){
294 $this->customName = $display->getString(self::TAG_DISPLAY_NAME, $this->customName);
295 $lore = $display->getListTag(self::TAG_DISPLAY_LORE);
296 if($lore !==
null && $lore->getTagType() === NBT::TAG_String){
298 foreach($lore as $t){
299 $this->lore[] = $t->getValue();
305 $enchantments = $tag->
getListTag(self::TAG_ENCH);
306 if($enchantments !==
null && $enchantments->getTagType() === NBT::TAG_Compound){
308 foreach($enchantments as $enchantment){
309 $magicNumber = $enchantment->getShort(self::TAG_ENCH_ID, -1);
310 $level = $enchantment->getShort(self::TAG_ENCH_LVL, 0);
314 $type = EnchantmentIdMap::getInstance()->fromId($magicNumber);
316 $this->addEnchantment(
new EnchantmentInstance($type, $level));
321 $this->blockEntityTag = $tag->
getCompoundTag(self::TAG_BLOCK_ENTITY_TAG);
323 $this->canPlaceOn = [];
324 $canPlaceOn = $tag->
getListTag(self::TAG_CAN_PLACE_ON);
325 if($canPlaceOn !==
null && $canPlaceOn->getTagType() === NBT::TAG_String){
327 foreach($canPlaceOn as $entry){
328 $this->canPlaceOn[$entry->getValue()] = $entry->getValue();
331 $this->canDestroy = [];
332 $canDestroy = $tag->
getListTag(self::TAG_CAN_DESTROY);
333 if($canDestroy !==
null && $canDestroy->getTagType() === NBT::TAG_String){
335 foreach($canDestroy as $entry){
336 $this->canDestroy[$entry->getValue()] = $entry->getValue();
340 $this->keepOnDeath = $tag->getByte(self::TAG_KEEP_ON_DEATH, 0) !== 0;
343 protected function serializeCompoundTag(CompoundTag $tag) : void{
344 $display = $tag->getCompoundTag(self::TAG_DISPLAY);
346 if($this->customName !==
""){
347 $display ??=
new CompoundTag();
348 $display->setString(self::TAG_DISPLAY_NAME, $this->customName);
350 $display?->removeTag(self::TAG_DISPLAY_NAME);
353 if(count($this->lore) > 0){
354 $loreTag =
new ListTag();
355 foreach($this->lore as $line){
356 $loreTag->push(
new StringTag($line));
358 $display ??=
new CompoundTag();
359 $display->setTag(self::TAG_DISPLAY_LORE, $loreTag);
361 $display?->removeTag(self::TAG_DISPLAY_LORE);
363 $display !==
null && $display->count() > 0 ?
364 $tag->setTag(self::TAG_DISPLAY, $display) :
365 $tag->removeTag(self::TAG_DISPLAY);
367 if(count($this->enchantments) > 0){
368 $ench =
new ListTag();
369 $enchantmentIdMap = EnchantmentIdMap::getInstance();
370 foreach($this->enchantments as $enchantmentInstance){
371 $ench->push(CompoundTag::create()
372 ->setShort(self::TAG_ENCH_ID, $enchantmentIdMap->toId($enchantmentInstance->getType()))
373 ->setShort(self::TAG_ENCH_LVL, $enchantmentInstance->getLevel())
376 $tag->setTag(self::TAG_ENCH, $ench);
378 $tag->removeTag(self::TAG_ENCH);
381 $this->blockEntityTag !==
null ?
382 $tag->setTag(self::TAG_BLOCK_ENTITY_TAG, clone $this->blockEntityTag) :
383 $tag->removeTag(self::TAG_BLOCK_ENTITY_TAG);
385 if(count($this->canPlaceOn) > 0){
386 $canPlaceOn =
new ListTag();
387 foreach($this->canPlaceOn as $item){
388 $canPlaceOn->push(
new StringTag($item));
390 $tag->setTag(self::TAG_CAN_PLACE_ON, $canPlaceOn);
392 $tag->removeTag(self::TAG_CAN_PLACE_ON);
394 if(count($this->canDestroy) > 0){
395 $canDestroy =
new ListTag();
396 foreach($this->canDestroy as $item){
397 $canDestroy->push(
new StringTag($item));
399 $tag->setTag(self::TAG_CAN_DESTROY, $canDestroy);
401 $tag->removeTag(self::TAG_CAN_DESTROY);
404 if($this->keepOnDeath){
405 $tag->setByte(self::TAG_KEEP_ON_DEATH, 1);
407 $tag->removeTag(self::TAG_KEEP_ON_DEATH);
411 public function getCount() : int{
419 $this->count = $count;
431 if($count > $this->count){
432 throw new \InvalidArgumentException(
"Cannot pop $count items from a stack of $this->count");
436 $item->count = $count;
438 $this->count -= $count;
443 public function isNull() : bool{
444 return $this->count <= 0;
451 return $this->hasCustomName() ? $this->getCustomName() : $this->getVanillaName();
471 return $this->enchantmentTags;
484 final public function canBePlaced() : bool{
485 return $this->getBlock()->canBePlaced();
495 final public function getTypeId() : int{
496 return $this->identifier->getTypeId();
499 final public function getStateId() : int{
500 return morton2d_encode($this->identifier->getTypeId(), $this->computeStateData());
503 private function computeStateData() : int{
504 $writer = new RuntimeDataWriter(16);
505 $this->describeState($writer);
506 return $writer->getValue();
520 public function getMaxStackSize() : int{
581 public function getMiningEfficiency(
bool $isCorrectTool) : float{
663 final public function equals(
Item $item,
bool $checkDamage =
true,
bool $checkCompound =
true) : bool{
664 return $this->getStateId() === $item->getStateId() &&
665 (!$checkCompound || $this->getNamedTag()->equals($item->getNamedTag()));
672 return $this->equals($other, true, true);
679 return $this->canStackWith($other) && $this->count === $other->count;
682 final public function __toString() : string{
683 return
"Item " . $this->name .
" (" . $this->getTypeId() .
":" . $this->computeStateData() .
")x" . $this->count . ($this->hasNamedTag() ?
" tags:0x" . base64_encode((new
LittleEndianNbtSerializer())->write(new
TreeRoot($this->getNamedTag()))) :
"");
690 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.");
705 if(isset($data[
"nbt"])){
707 }elseif(isset($data[
"nbt_hex"])){
708 $nbt = hex2bin($data[
"nbt_hex"]);
709 }elseif(isset($data[
"nbt_b64"])){
710 $nbt = base64_decode($data[
"nbt_b64"],
true);
713 $itemStackData = GlobalItemDataHandlers::getUpgrader()->upgradeItemTypeDataInt(
715 (
int) ($data[
"damage"] ?? 0),
716 (
int) ($data[
"count"] ?? 1),
717 $nbt !==
"" ? (
new LittleEndianNbtSerializer())->read($nbt)->mustGetCompoundTag() :
null
721 return GlobalItemDataHandlers::getDeserializer()->deserializeStack($itemStackData);
722 }
catch(ItemTypeDeserializeException $e){
723 throw new SavedDataLoadingException($e->getMessage(), 0, $e);
733 return
GlobalItemDataHandlers::getSerializer()->serializeStack($this, $slot !== -1 ? $slot : null)->toNbt();
742 if($itemData ===
null){
743 return VanillaItems::AIR();
747 return GlobalItemDataHandlers::getDeserializer()->deserializeStack($itemData);
748 }
catch(ItemTypeDeserializeException $e){
749 throw new SavedDataLoadingException($e->getMessage(), 0, $e);
753 public function __clone(){
754 $this->nbt = clone $this->nbt;
755 if($this->blockEntityTag !==
null){
756 $this->blockEntityTag = clone $this->blockEntityTag;
static legacyJsonDeserialize(array $data)
getBlock(?int $clickedFace=null)
describeState(RuntimeDataDescriber $w)
nbtSerialize(int $slot=-1)
onAttackEntity(Entity $victim, array &$returnedItems)
getBlockToolHarvestLevel()
setCustomBlockData(CompoundTag $compound)
canStackWith(Item $other)
equals(Item $item, bool $checkDamage=true, bool $checkCompound=true)
deserializeCompoundTag(CompoundTag $tag)
CompoundTag $blockEntityTag
onReleaseUsing(Player $player, array &$returnedItems)
setNamedTag(CompoundTag $tag)
static nbtDeserialize(CompoundTag $tag)
onClickAir(Player $player, Vector3 $directionVector, array &$returnedItems)
setCanPlaceOn(array $canPlaceOn)
onTickWorn(Living $entity)
__construct(private ItemIdentifier $identifier, protected string $name="Unknown", private array $enchantmentTags=[])
onInteractBlock(Player $player, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, array &$returnedItems)
setCanDestroy(array $canDestroy)
onDestroyBlock(Block $block, array &$returnedItems)
setCustomName(string $name)
onInteractEntity(Player $player, Entity $entity, Vector3 $clickVector)
getCompoundTag(string $name)
trait ItemEnchantmentHandlingTrait