PocketMine-MP 5.33.2 git-919492bdcad8510eb6606439eb77e1c604f1d1ea
Loading...
Searching...
No Matches
Item.php
1<?php
2
3/*
4 *
5 * ____ _ _ __ __ _ __ __ ____
6 * | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
7 * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
8 * | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
9 * |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
10 *
11 * This program is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU Lesser General Public License as published by
13 * the Free Software Foundation, either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * @author PocketMine Team
17 * @link http://www.pocketmine.net/
18 *
19 *
20 */
21
22declare(strict_types=1);
23
27namespace pocketmine\item;
28
54use function base64_decode;
55use function base64_encode;
56use function count;
57use function gettype;
58use function hex2bin;
59use function is_string;
60use function morton2d_encode;
61
62class Item implements \JsonSerializable{
64
65 public const TAG_ENCH = "ench";
66 private const TAG_ENCH_ID = "id"; //TAG_Short
67 private const TAG_ENCH_LVL = "lvl"; //TAG_Short
68
69 public const TAG_DISPLAY = "display";
70 public const TAG_BLOCK_ENTITY_TAG = "BlockEntityTag";
71
72 public const TAG_DISPLAY_NAME = "Name";
73 public const TAG_DISPLAY_LORE = "Lore";
74
75 public const TAG_KEEP_ON_DEATH = "minecraft:keep_on_death";
76
77 private const TAG_CAN_PLACE_ON = "CanPlaceOn"; //TAG_List<TAG_String>
78 private const TAG_CAN_DESTROY = "CanDestroy"; //TAG_List<TAG_String>
79
80 private CompoundTag $nbt;
81
82 protected int $count = 1;
83
84 //TODO: this stuff should be moved to itemstack properties, not mushed in with type properties
85
86 protected string $customName = "";
88 protected array $lore = [];
90 protected ?CompoundTag $blockEntityTag = null;
91
96 protected array $canPlaceOn = [];
101 protected array $canDestroy = [];
102
103 protected bool $keepOnDeath = false;
104
115 public function __construct(
116 private ItemIdentifier $identifier,
117 protected string $name = "Unknown",
118 private array $enchantmentTags = []
119 ){
120 $this->nbt = new CompoundTag();
121 }
122
123 public function hasCustomBlockData() : bool{
124 return $this->blockEntityTag !== null;
125 }
126
130 public function clearCustomBlockData() : Item{
131 $this->blockEntityTag = null;
132 return $this;
133 }
134
138 public function setCustomBlockData(CompoundTag $compound) : Item{
139 $this->blockEntityTag = clone $compound;
140
141 return $this;
142 }
143
144 public function getCustomBlockData() : ?CompoundTag{
145 return $this->blockEntityTag;
146 }
147
148 public function hasCustomName() : bool{
149 return $this->customName !== "";
150 }
151
152 public function getCustomName() : string{
153 return $this->customName;
154 }
155
159 public function setCustomName(string $name) : Item{
160 Utils::checkUTF8($name);
161 $this->customName = $name;
162 return $this;
163 }
164
168 public function clearCustomName() : Item{
169 $this->setCustomName("");
170 return $this;
171 }
172
176 public function getLore() : array{
177 return $this->lore;
178 }
179
185 public function setLore(array $lines) : Item{
186 foreach($lines as $line){
187 if(!is_string($line)){
188 throw new \TypeError("Expected string[], but found " . gettype($line) . " in given array");
189 }
190 Utils::checkUTF8($line);
191 }
192 $this->lore = $lines;
193 return $this;
194 }
195
200 public function getCanPlaceOn() : array{
201 return $this->canPlaceOn;
202 }
203
207 public function setCanPlaceOn(array $canPlaceOn) : Item{
208 $this->canPlaceOn = [];
209 foreach($canPlaceOn as $value){
210 $this->canPlaceOn[$value] = $value;
211 }
212 return $this;
213 }
214
219 public function getCanDestroy() : array{
220 return $this->canDestroy;
221 }
222
226 public function setCanDestroy(array $canDestroy) : Item{
227 $this->canDestroy = [];
228 foreach($canDestroy as $value){
229 $this->canDestroy[$value] = $value;
230 }
231 return $this;
232 }
233
237 public function keepOnDeath() : bool{
238 return $this->keepOnDeath;
239 }
240
241 public function setKeepOnDeath(bool $keepOnDeath) : Item{
242 $this->keepOnDeath = $keepOnDeath;
243 return $this;
244 }
245
249 public function hasNamedTag() : bool{
250 return $this->getNamedTag()->count() > 0;
251 }
252
257 public function getNamedTag() : CompoundTag{
258 $this->serializeCompoundTag($this->nbt);
259 return $this->nbt;
260 }
261
268 public function setNamedTag(CompoundTag $tag) : Item{
269 if($tag->getCount() === 0){
270 return $this->clearNamedTag();
271 }
272
273 $this->nbt = clone $tag;
274 $this->deserializeCompoundTag($this->nbt);
275
276 return $this;
277 }
278
284 public function clearNamedTag() : Item{
285 $this->nbt = new CompoundTag();
286 $this->deserializeCompoundTag($this->nbt);
287 return $this;
288 }
289
293 protected function deserializeCompoundTag(CompoundTag $tag) : void{
294 $this->customName = "";
295 $this->lore = [];
296
297 $display = $tag->getCompoundTag(self::TAG_DISPLAY);
298 if($display !== null){
299 $this->customName = $display->getString(self::TAG_DISPLAY_NAME, $this->customName);
300 $lore = $display->getListTag(self::TAG_DISPLAY_LORE);
301 if($lore !== null && $lore->getTagType() === NBT::TAG_String){
303 foreach($lore as $t){
304 $this->lore[] = $t->getValue();
305 }
306 }
307 }
308
309 $this->removeEnchantments();
310 $enchantments = $tag->getListTag(self::TAG_ENCH);
311 if($enchantments !== null && $enchantments->getTagType() === NBT::TAG_Compound){
313 foreach($enchantments as $enchantment){
314 $magicNumber = $enchantment->getShort(self::TAG_ENCH_ID, -1);
315 $level = $enchantment->getShort(self::TAG_ENCH_LVL, 0);
316 if($level <= 0){
317 continue;
318 }
319 $type = EnchantmentIdMap::getInstance()->fromId($magicNumber);
320 if($type !== null){
321 $this->addEnchantment(new EnchantmentInstance($type, $level));
322 }
323 }
324 }
325
326 $this->blockEntityTag = $tag->getCompoundTag(self::TAG_BLOCK_ENTITY_TAG);
327
328 $this->canPlaceOn = [];
329 $canPlaceOn = $tag->getListTag(self::TAG_CAN_PLACE_ON);
330 if($canPlaceOn !== null && $canPlaceOn->getTagType() === NBT::TAG_String){
332 foreach($canPlaceOn as $entry){
333 $this->canPlaceOn[$entry->getValue()] = $entry->getValue();
334 }
335 }
336 $this->canDestroy = [];
337 $canDestroy = $tag->getListTag(self::TAG_CAN_DESTROY);
338 if($canDestroy !== null && $canDestroy->getTagType() === NBT::TAG_String){
340 foreach($canDestroy as $entry){
341 $this->canDestroy[$entry->getValue()] = $entry->getValue();
342 }
343 }
344
345 $this->keepOnDeath = $tag->getByte(self::TAG_KEEP_ON_DEATH, 0) !== 0;
346 }
347
348 protected function serializeCompoundTag(CompoundTag $tag) : void{
349 $display = $tag->getCompoundTag(self::TAG_DISPLAY);
350
351 if($this->customName !== ""){
352 $display ??= new CompoundTag();
353 $display->setString(self::TAG_DISPLAY_NAME, $this->customName);
354 }else{
355 $display?->removeTag(self::TAG_DISPLAY_NAME);
356 }
357
358 if(count($this->lore) > 0){
359 $loreTag = new ListTag();
360 foreach($this->lore as $line){
361 $loreTag->push(new StringTag($line));
362 }
363 $display ??= new CompoundTag();
364 $display->setTag(self::TAG_DISPLAY_LORE, $loreTag);
365 }else{
366 $display?->removeTag(self::TAG_DISPLAY_LORE);
367 }
368 $display !== null && $display->count() > 0 ?
369 $tag->setTag(self::TAG_DISPLAY, $display) :
370 $tag->removeTag(self::TAG_DISPLAY);
371
372 if(count($this->enchantments) > 0){
373 $ench = new ListTag();
374 $enchantmentIdMap = EnchantmentIdMap::getInstance();
375 foreach($this->enchantments as $enchantmentInstance){
376 $ench->push(CompoundTag::create()
377 ->setShort(self::TAG_ENCH_ID, $enchantmentIdMap->toId($enchantmentInstance->getType()))
378 ->setShort(self::TAG_ENCH_LVL, $enchantmentInstance->getLevel())
379 );
380 }
381 $tag->setTag(self::TAG_ENCH, $ench);
382 }else{
383 $tag->removeTag(self::TAG_ENCH);
384 }
385
386 $this->blockEntityTag !== null ?
387 $tag->setTag(self::TAG_BLOCK_ENTITY_TAG, clone $this->blockEntityTag) :
388 $tag->removeTag(self::TAG_BLOCK_ENTITY_TAG);
389
390 if(count($this->canPlaceOn) > 0){
391 $canPlaceOn = new ListTag();
392 foreach($this->canPlaceOn as $item){
393 $canPlaceOn->push(new StringTag($item));
394 }
395 $tag->setTag(self::TAG_CAN_PLACE_ON, $canPlaceOn);
396 }else{
397 $tag->removeTag(self::TAG_CAN_PLACE_ON);
398 }
399 if(count($this->canDestroy) > 0){
400 $canDestroy = new ListTag();
401 foreach($this->canDestroy as $item){
402 $canDestroy->push(new StringTag($item));
403 }
404 $tag->setTag(self::TAG_CAN_DESTROY, $canDestroy);
405 }else{
406 $tag->removeTag(self::TAG_CAN_DESTROY);
407 }
408
409 if($this->keepOnDeath){
410 $tag->setByte(self::TAG_KEEP_ON_DEATH, 1);
411 }else{
412 $tag->removeTag(self::TAG_KEEP_ON_DEATH);
413 }
414 }
415
416 public function getCount() : int{
417 return $this->count;
418 }
419
423 public function setCount(int $count) : Item{
424 $this->count = $count;
425
426 return $this;
427 }
428
435 public function pop(int $count = 1) : Item{
436 if($count > $this->count){
437 throw new \InvalidArgumentException("Cannot pop $count items from a stack of $this->count");
438 }
439
440 $item = clone $this;
441 $item->count = $count;
442
443 $this->count -= $count;
444
445 return $item;
446 }
447
448 public function isNull() : bool{
449 return $this->count <= 0;
450 }
451
455 final public function getName() : string{
456 return $this->hasCustomName() ? $this->getCustomName() : $this->getVanillaName();
457 }
458
462 public function getVanillaName() : string{
463 return $this->name;
464 }
465
475 public function getEnchantmentTags() : array{
476 return $this->enchantmentTags;
477 }
478
485 public function getEnchantability() : int{
486 return 1;
487 }
488
489 final public function canBePlaced() : bool{
490 return $this->getBlock()->canBePlaced();
491 }
492
493 protected final function tryPlacementTransaction(Block $blockPlace, Block $blockReplace, Block $blockClicked, Facing $face, Vector3 $clickVector, ?Player $player) : ?BlockTransaction{
494 $position = $blockReplace->getPosition();
495 $blockPlace->position($position->getWorld(), $position->getFloorX(), $position->getFloorY(), $position->getFloorZ());
496 if(!$blockPlace->canBePlacedAt($blockReplace, $clickVector, $face, $blockReplace->getPosition()->equals($blockClicked->getPosition()))){
497 return null;
498 }
499 $transaction = new BlockTransaction($position->getWorld());
500 return $blockPlace->place($transaction, $this, $blockReplace, $blockClicked, $face, $clickVector, $player) ? $transaction : null;
501 }
502
503 public function getPlacementTransaction(Block $blockReplace, Block $blockClicked, Facing $face, Vector3 $clickVector, ?Player $player = null) : ?BlockTransaction{
504 return $this->tryPlacementTransaction($this->getBlock($face), $blockReplace, $blockClicked, $face, $clickVector, $player);
505 }
506
510 public function getBlock(?Facing $clickedFace = null) : Block{
511 return VanillaBlocks::AIR();
512 }
513
514 final public function getTypeId() : int{
515 return $this->identifier->getTypeId();
516 }
517
518 final public function getStateId() : int{
519 return morton2d_encode($this->identifier->getTypeId(), $this->computeStateData());
520 }
521
522 private function computeStateData() : int{
523 $writer = new RuntimeDataWriter(16); //TODO: max bits should be a constant instead of being hardcoded all over the place
524 $this->describeState($writer);
525 return $writer->getValue();
526 }
527
532 protected function describeState(RuntimeDataDescriber $w) : void{
533 //NOOP
534 }
535
539 public function getMaxStackSize() : int{
540 return 64;
541 }
542
546 public function getFuelTime() : int{
547 return 0;
548 }
549
553 public function getFuelResidue() : Item{
554 $item = clone $this;
555 $item->pop();
556
557 return $item;
558 }
559
563 public function isFireProof() : bool{
564 return false;
565 }
566
570 public function getAttackPoints() : int{
571 return 1;
572 }
573
577 public function getDefensePoints() : int{
578 return 0;
579 }
580
585 public function getBlockToolType() : int{
586 return BlockToolType::NONE;
587 }
588
596 public function getBlockToolHarvestLevel() : int{
597 return 0;
598 }
599
600 public function getMiningEfficiency(bool $isCorrectTool) : float{
601 return 1;
602 }
603
609 public function onInteractBlock(Player $player, Block $blockReplace, Block $blockClicked, Facing $face, Vector3 $clickVector, array &$returnedItems) : ItemUseResult{
610 return ItemUseResult::NONE;
611 }
612
619 public function onClickAir(Player $player, Vector3 $directionVector, array &$returnedItems) : ItemUseResult{
620 return ItemUseResult::NONE;
621 }
622
629 public function onReleaseUsing(Player $player, array &$returnedItems) : ItemUseResult{
630 return ItemUseResult::NONE;
631 }
632
638 public function onDestroyBlock(Block $block, array &$returnedItems) : bool{
639 return false;
640 }
641
647 public function onAttackEntity(Entity $victim, array &$returnedItems) : bool{
648 return false;
649 }
650
655 public function onTickWorn(Living $entity) : bool{
656 return false;
657 }
658
665 public function onInteractEntity(Player $player, Entity $entity, Vector3 $clickVector) : bool{
666 return false;
667 }
668
672 public function getCooldownTicks() : int{
673 return 0;
674 }
675
686 public function getCooldownTag() : ?string{
687 return null;
688 }
689
696 final public function equals(Item $item, bool $checkDamage = true, bool $checkCompound = true) : bool{
697 return $this->getStateId() === $item->getStateId() &&
698 (!$checkCompound || $this->getNamedTag()->equals($item->getNamedTag()));
699 }
700
704 final public function canStackWith(Item $other) : bool{
705 return $this->equals($other, true, true);
706 }
707
711 final public function equalsExact(Item $other) : bool{
712 return $this->canStackWith($other) && $this->count === $other->count;
713 }
714
715 final public function __toString() : string{
716 return "Item " . $this->name . " (" . $this->getTypeId() . ":" . $this->computeStateData() . ")x" . $this->count . ($this->hasNamedTag() ? " tags:0x" . base64_encode((new LittleEndianNbtSerializer())->write(new TreeRoot($this->getNamedTag()))) : "");
717 }
718
722 public function jsonSerialize() : array{
723 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.");
724 }
725
734 final public static function legacyJsonDeserialize(array $data) : Item{
735 $nbt = "";
736
737 //Backwards compatibility
738 if(isset($data["nbt"])){
739 $nbt = $data["nbt"];
740 }elseif(isset($data["nbt_hex"])){
741 $nbt = hex2bin($data["nbt_hex"]);
742 }elseif(isset($data["nbt_b64"])){
743 $nbt = base64_decode($data["nbt_b64"], true);
744 }
745
746 $itemStackData = GlobalItemDataHandlers::getUpgrader()->upgradeItemTypeDataInt(
747 (int) $data["id"],
748 (int) ($data["damage"] ?? 0),
749 (int) ($data["count"] ?? 1),
750 $nbt !== "" ? (new LittleEndianNbtSerializer())->read($nbt)->mustGetCompoundTag() : null
751 );
752
753 try{
754 return GlobalItemDataHandlers::getDeserializer()->deserializeStack($itemStackData);
755 }catch(ItemTypeDeserializeException $e){
756 throw new SavedDataLoadingException($e->getMessage(), 0, $e);
757 }
758 }
759
765 public function nbtSerialize(int $slot = -1) : CompoundTag{
766 return GlobalItemDataHandlers::getSerializer()->serializeStack($this, $slot !== -1 ? $slot : null)->toNbt();
767 }
768
773 public static function nbtDeserialize(CompoundTag $tag) : Item{
774 $itemData = GlobalItemDataHandlers::getUpgrader()->upgradeItemStackNbt($tag);
775 if($itemData === null){
776 return VanillaItems::AIR();
777 }
778
779 try{
780 return GlobalItemDataHandlers::getDeserializer()->deserializeStack($itemData);
781 }catch(ItemTypeDeserializeException $e){
782 throw new SavedDataLoadingException($e->getMessage(), 0, $e);
783 }
784 }
785
786 public function __clone(){
787 $this->nbt = clone $this->nbt;
788 if($this->blockEntityTag !== null){
789 $this->blockEntityTag = clone $this->blockEntityTag;
790 }
791 }
792}
static legacyJsonDeserialize(array $data)
Definition Item.php:734
describeState(RuntimeDataDescriber $w)
Definition Item.php:532
nbtSerialize(int $slot=-1)
Definition Item.php:765
onAttackEntity(Entity $victim, array &$returnedItems)
Definition Item.php:647
setCustomBlockData(CompoundTag $compound)
Definition Item.php:138
canStackWith(Item $other)
Definition Item.php:704
equals(Item $item, bool $checkDamage=true, bool $checkCompound=true)
Definition Item.php:696
deserializeCompoundTag(CompoundTag $tag)
Definition Item.php:293
CompoundTag $blockEntityTag
Definition Item.php:90
pop(int $count=1)
Definition Item.php:435
onReleaseUsing(Player $player, array &$returnedItems)
Definition Item.php:629
setNamedTag(CompoundTag $tag)
Definition Item.php:268
static nbtDeserialize(CompoundTag $tag)
Definition Item.php:773
setCount(int $count)
Definition Item.php:423
setLore(array $lines)
Definition Item.php:185
onClickAir(Player $player, Vector3 $directionVector, array &$returnedItems)
Definition Item.php:619
setCanPlaceOn(array $canPlaceOn)
Definition Item.php:207
getBlock(?Facing $clickedFace=null)
Definition Item.php:510
onTickWorn(Living $entity)
Definition Item.php:655
__construct(private ItemIdentifier $identifier, protected string $name="Unknown", private array $enchantmentTags=[])
Definition Item.php:115
setCanDestroy(array $canDestroy)
Definition Item.php:226
onDestroyBlock(Block $block, array &$returnedItems)
Definition Item.php:638
setCustomName(string $name)
Definition Item.php:159
onInteractBlock(Player $player, Block $blockReplace, Block $blockClicked, Facing $face, Vector3 $clickVector, array &$returnedItems)
Definition Item.php:609
equalsExact(Item $other)
Definition Item.php:711
onInteractEntity(Player $player, Entity $entity, Vector3 $clickVector)
Definition Item.php:665