61class Item implements \JsonSerializable{
64 public const TAG_ENCH =
"ench";
65 private const TAG_ENCH_ID =
"id";
66 private const TAG_ENCH_LVL =
"lvl";
68 public const TAG_DISPLAY =
"display";
69 public const TAG_BLOCK_ENTITY_TAG =
"BlockEntityTag";
71 public const TAG_DISPLAY_NAME =
"Name";
72 public const TAG_DISPLAY_LORE =
"Lore";
74 public const TAG_KEEP_ON_DEATH =
"minecraft:keep_on_death";
76 private const TAG_CAN_PLACE_ON =
"CanPlaceOn";
77 private const TAG_CAN_DESTROY =
"CanDestroy";
81 protected int $count = 1;
85 protected string $customName =
"";
87 protected array $lore = [];
95 protected array $canPlaceOn = [];
100 protected array $canDestroy = [];
102 protected bool $keepOnDeath =
false;
116 protected string $name =
"Unknown",
117 private array $enchantmentTags = []
122 public function hasCustomBlockData() : bool{
123 return $this->blockEntityTag !== null;
130 $this->blockEntityTag =
null;
138 $this->blockEntityTag = clone $compound;
143 public function getCustomBlockData() : ?
CompoundTag{
144 return $this->blockEntityTag;
147 public function hasCustomName() : bool{
148 return $this->customName !==
"";
151 public function getCustomName() : string{
152 return $this->customName;
159 Utils::checkUTF8($name);
160 $this->customName = $name;
168 $this->setCustomName(
"");
185 foreach($lines as $line){
186 if(!is_string($line)){
187 throw new \TypeError(
"Expected string[], but found " . gettype($line) .
" in given array");
189 Utils::checkUTF8($line);
191 $this->lore = $lines;
200 return $this->canPlaceOn;
207 $this->canPlaceOn = [];
208 foreach($canPlaceOn as $value){
209 $this->canPlaceOn[$value] = $value;
218 return $this->canDestroy;
225 $this->canDestroy = [];
226 foreach($canDestroy as $value){
227 $this->canDestroy[$value] = $value;
235 return $this->keepOnDeath;
238 public function setKeepOnDeath(
bool $keepOnDeath) : void{
239 $this->keepOnDeath = $keepOnDeath;
246 return $this->getNamedTag()->count() > 0;
254 $this->serializeCompoundTag($this->nbt);
265 if($tag->getCount() === 0){
266 return $this->clearNamedTag();
269 $this->nbt = clone $tag;
270 $this->deserializeCompoundTag($this->nbt);
282 $this->deserializeCompoundTag($this->nbt);
290 $this->customName =
"";
294 if($display !==
null){
295 $this->customName = $display->getString(self::TAG_DISPLAY_NAME, $this->customName);
296 $lore = $display->getListTag(self::TAG_DISPLAY_LORE, StringTag::class);
298 foreach($lore as $t){
299 $this->lore[] = $t->getValue();
305 $enchantments = $tag->
getListTag(self::TAG_ENCH, CompoundTag::class);
306 if($enchantments !==
null){
307 foreach($enchantments as $enchantment){
308 $magicNumber = $enchantment->getShort(self::TAG_ENCH_ID, -1);
309 $level = $enchantment->getShort(self::TAG_ENCH_LVL, 0);
313 $type = EnchantmentIdMap::getInstance()->fromId($magicNumber);
315 $this->addEnchantment(
new EnchantmentInstance($type, $level));
320 $this->blockEntityTag = $tag->
getCompoundTag(self::TAG_BLOCK_ENTITY_TAG);
322 $this->canPlaceOn = [];
323 $canPlaceOn = $tag->
getListTag(self::TAG_CAN_PLACE_ON, StringTag::class);
324 if($canPlaceOn !==
null){
325 foreach($canPlaceOn as $entry){
326 $this->canPlaceOn[$entry->getValue()] = $entry->getValue();
329 $this->canDestroy = [];
330 $canDestroy = $tag->
getListTag(self::TAG_CAN_DESTROY, StringTag::class);
331 if($canDestroy !==
null){
332 foreach($canDestroy as $entry){
333 $this->canDestroy[$entry->getValue()] = $entry->getValue();
337 $this->keepOnDeath = $tag->getByte(self::TAG_KEEP_ON_DEATH, 0) !== 0;
340 protected function serializeCompoundTag(CompoundTag $tag) : void{
341 $display = $tag->getCompoundTag(self::TAG_DISPLAY);
343 if($this->customName !==
""){
344 $display ??=
new CompoundTag();
345 $display->setString(self::TAG_DISPLAY_NAME, $this->customName);
347 $display?->removeTag(self::TAG_DISPLAY_NAME);
350 if(count($this->lore) > 0){
351 $loreTag =
new ListTag();
352 foreach($this->lore as $line){
353 $loreTag->push(
new StringTag($line));
355 $display ??=
new CompoundTag();
356 $display->setTag(self::TAG_DISPLAY_LORE, $loreTag);
358 $display?->removeTag(self::TAG_DISPLAY_LORE);
360 $display !==
null && $display->count() > 0 ?
361 $tag->setTag(self::TAG_DISPLAY, $display) :
362 $tag->removeTag(self::TAG_DISPLAY);
364 if(count($this->enchantments) > 0){
365 $ench =
new ListTag();
366 $enchantmentIdMap = EnchantmentIdMap::getInstance();
367 foreach($this->enchantments as $enchantmentInstance){
368 $ench->push(CompoundTag::create()
369 ->setShort(self::TAG_ENCH_ID, $enchantmentIdMap->toId($enchantmentInstance->getType()))
370 ->setShort(self::TAG_ENCH_LVL, $enchantmentInstance->getLevel())
373 $tag->setTag(self::TAG_ENCH, $ench);
375 $tag->removeTag(self::TAG_ENCH);
378 $this->blockEntityTag !==
null ?
379 $tag->setTag(self::TAG_BLOCK_ENTITY_TAG, clone $this->blockEntityTag) :
380 $tag->removeTag(self::TAG_BLOCK_ENTITY_TAG);
382 if(count($this->canPlaceOn) > 0){
383 $canPlaceOn =
new ListTag();
384 foreach($this->canPlaceOn as $item){
385 $canPlaceOn->push(
new StringTag($item));
387 $tag->setTag(self::TAG_CAN_PLACE_ON, $canPlaceOn);
389 $tag->removeTag(self::TAG_CAN_PLACE_ON);
391 if(count($this->canDestroy) > 0){
392 $canDestroy =
new ListTag();
393 foreach($this->canDestroy as $item){
394 $canDestroy->push(
new StringTag($item));
396 $tag->setTag(self::TAG_CAN_DESTROY, $canDestroy);
398 $tag->removeTag(self::TAG_CAN_DESTROY);
401 if($this->keepOnDeath){
402 $tag->setByte(self::TAG_KEEP_ON_DEATH, 1);
404 $tag->removeTag(self::TAG_KEEP_ON_DEATH);
408 public function getCount() : int{
416 $this->count = $count;
428 if($count > $this->count){
429 throw new \InvalidArgumentException(
"Cannot pop $count items from a stack of $this->count");
433 $item->count = $count;
435 $this->count -= $count;
440 public function isNull() : bool{
441 return $this->count <= 0;
448 return $this->hasCustomName() ? $this->getCustomName() : $this->getVanillaName();
468 return $this->enchantmentTags;
481 final public function canBePlaced() : bool{
482 return $this->getBlock()->canBePlaced();
485 protected final function tryPlacementTransaction(Block $blockPlace, Block $blockReplace, Block $blockClicked,
int $face, Vector3 $clickVector, ?
Player $player) : ?BlockTransaction{
486 $position = $blockReplace->getPosition();
487 $blockPlace->position($position->getWorld(), $position->getFloorX(), $position->getFloorY(), $position->getFloorZ());
488 if(!$blockPlace->canBePlacedAt($blockReplace, $clickVector, $face, $blockReplace->getPosition()->equals($blockClicked->getPosition()))){
491 $transaction =
new BlockTransaction($position->getWorld());
492 return $blockPlace->place($transaction, $this, $blockReplace, $blockClicked, $face, $clickVector, $player) ? $transaction :
null;
495 public function getPlacementTransaction(Block $blockReplace, Block $blockClicked,
int $face, Vector3 $clickVector, ?Player $player =
null) : ?BlockTransaction{
496 return $this->tryPlacementTransaction($this->getBlock($face), $blockReplace, $blockClicked, $face, $clickVector, $player);
506 final public function getTypeId() : int{
507 return $this->identifier->getTypeId();
510 final public function getStateId() : int{
511 return morton2d_encode($this->identifier->getTypeId(), $this->computeStateData());
514 private function computeStateData() : int{
515 $writer = new RuntimeDataWriter(16);
516 $this->describeState($writer);
517 return $writer->getValue();
531 public function getMaxStackSize() : int{
592 public function getMiningEfficiency(
bool $isCorrectTool) : float{
688 final public function equals(
Item $item,
bool $checkDamage =
true,
bool $checkCompound =
true) : bool{
689 return $this->getStateId() === $item->getStateId() &&
690 (!$checkCompound || $this->getNamedTag()->equals($item->getNamedTag()));
697 return $this->equals($other, true, true);
704 return $this->canStackWith($other) && $this->count === $other->count;
707 final public function __toString() : string{
708 return
"Item " . $this->name .
" (" . $this->getTypeId() .
":" . $this->computeStateData() .
")x" . $this->count . ($this->hasNamedTag() ?
" tags:0x" . base64_encode((new
LittleEndianNbtSerializer())->write(new
TreeRoot($this->getNamedTag()))) :
"");
715 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.");
730 if(isset($data[
"nbt"])){
732 }elseif(isset($data[
"nbt_hex"])){
733 $nbt = hex2bin($data[
"nbt_hex"]);
734 }elseif(isset($data[
"nbt_b64"])){
735 $nbt = base64_decode($data[
"nbt_b64"],
true);
738 $itemStackData = GlobalItemDataHandlers::getUpgrader()->upgradeItemTypeDataInt(
740 (
int) ($data[
"damage"] ?? 0),
741 (
int) ($data[
"count"] ?? 1),
742 $nbt !==
"" ? (
new LittleEndianNbtSerializer())->read($nbt)->mustGetCompoundTag() :
null
746 return GlobalItemDataHandlers::getDeserializer()->deserializeStack($itemStackData);
747 }
catch(ItemTypeDeserializeException $e){
748 throw new SavedDataLoadingException($e->getMessage(), 0, $e);
758 return
GlobalItemDataHandlers::getSerializer()->serializeStack($this, $slot !== -1 ? $slot : null)->toNbt();
767 if($itemData ===
null){
768 return VanillaItems::AIR();
772 return GlobalItemDataHandlers::getDeserializer()->deserializeStack($itemData);
773 }
catch(ItemTypeDeserializeException $e){
774 throw new SavedDataLoadingException($e->getMessage(), 0, $e);
778 public function __clone(){
779 $this->nbt = clone $this->nbt;
780 if($this->blockEntityTag !==
null){
781 $this->blockEntityTag = clone $this->blockEntityTag;