PocketMine-MP 5.32.2 git-1ebd7d3960d713d56f77f610fe0c15cdee201069
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
52use function base64_decode;
53use function base64_encode;
54use function count;
55use function gettype;
56use function hex2bin;
57use function is_string;
58use function morton2d_encode;
59
60class Item implements \JsonSerializable{
62
63 public const TAG_ENCH = "ench";
64 private const TAG_ENCH_ID = "id"; //TAG_Short
65 private const TAG_ENCH_LVL = "lvl"; //TAG_Short
66
67 public const TAG_DISPLAY = "display";
68 public const TAG_BLOCK_ENTITY_TAG = "BlockEntityTag";
69
70 public const TAG_DISPLAY_NAME = "Name";
71 public const TAG_DISPLAY_LORE = "Lore";
72
73 public const TAG_KEEP_ON_DEATH = "minecraft:keep_on_death";
74
75 private const TAG_CAN_PLACE_ON = "CanPlaceOn"; //TAG_List<TAG_String>
76 private const TAG_CAN_DESTROY = "CanDestroy"; //TAG_List<TAG_String>
77
78 private CompoundTag $nbt;
79
80 protected int $count = 1;
81
82 //TODO: this stuff should be moved to itemstack properties, not mushed in with type properties
83
84 protected string $customName = "";
86 protected array $lore = [];
88 protected ?CompoundTag $blockEntityTag = null;
89
94 protected array $canPlaceOn = [];
99 protected array $canDestroy = [];
100
101 protected bool $keepOnDeath = false;
102
113 public function __construct(
114 private ItemIdentifier $identifier,
115 protected string $name = "Unknown",
116 private array $enchantmentTags = []
117 ){
118 $this->nbt = new CompoundTag();
119 }
120
121 public function hasCustomBlockData() : bool{
122 return $this->blockEntityTag !== null;
123 }
124
128 public function clearCustomBlockData() : Item{
129 $this->blockEntityTag = null;
130 return $this;
131 }
132
136 public function setCustomBlockData(CompoundTag $compound) : Item{
137 $this->blockEntityTag = clone $compound;
138
139 return $this;
140 }
141
142 public function getCustomBlockData() : ?CompoundTag{
143 return $this->blockEntityTag;
144 }
145
146 public function hasCustomName() : bool{
147 return $this->customName !== "";
148 }
149
150 public function getCustomName() : string{
151 return $this->customName;
152 }
153
157 public function setCustomName(string $name) : Item{
158 Utils::checkUTF8($name);
159 $this->customName = $name;
160 return $this;
161 }
162
166 public function clearCustomName() : Item{
167 $this->setCustomName("");
168 return $this;
169 }
170
174 public function getLore() : array{
175 return $this->lore;
176 }
177
183 public function setLore(array $lines) : Item{
184 foreach($lines as $line){
185 if(!is_string($line)){
186 throw new \TypeError("Expected string[], but found " . gettype($line) . " in given array");
187 }
188 Utils::checkUTF8($line);
189 }
190 $this->lore = $lines;
191 return $this;
192 }
193
198 public function getCanPlaceOn() : array{
199 return $this->canPlaceOn;
200 }
201
205 public function setCanPlaceOn(array $canPlaceOn) : Item{
206 $this->canPlaceOn = [];
207 foreach($canPlaceOn as $value){
208 $this->canPlaceOn[$value] = $value;
209 }
210 return $this;
211 }
212
217 public function getCanDestroy() : array{
218 return $this->canDestroy;
219 }
220
224 public function setCanDestroy(array $canDestroy) : Item{
225 $this->canDestroy = [];
226 foreach($canDestroy as $value){
227 $this->canDestroy[$value] = $value;
228 }
229 return $this;
230 }
231
235 public function keepOnDeath() : bool{
236 return $this->keepOnDeath;
237 }
238
239 public function setKeepOnDeath(bool $keepOnDeath) : Item{
240 $this->keepOnDeath = $keepOnDeath;
241 return $this;
242 }
243
247 public function hasNamedTag() : bool{
248 return $this->getNamedTag()->count() > 0;
249 }
250
255 public function getNamedTag() : CompoundTag{
256 $this->serializeCompoundTag($this->nbt);
257 return $this->nbt;
258 }
259
266 public function setNamedTag(CompoundTag $tag) : Item{
267 if($tag->getCount() === 0){
268 return $this->clearNamedTag();
269 }
270
271 $this->nbt = clone $tag;
272 $this->deserializeCompoundTag($this->nbt);
273
274 return $this;
275 }
276
282 public function clearNamedTag() : Item{
283 $this->nbt = new CompoundTag();
284 $this->deserializeCompoundTag($this->nbt);
285 return $this;
286 }
287
291 protected function deserializeCompoundTag(CompoundTag $tag) : void{
292 $this->customName = "";
293 $this->lore = [];
294
295 $display = $tag->getCompoundTag(self::TAG_DISPLAY);
296 if($display !== null){
297 $this->customName = $display->getString(self::TAG_DISPLAY_NAME, $this->customName);
298 $lore = $display->getListTag(self::TAG_DISPLAY_LORE);
299 if($lore !== null && $lore->getTagType() === NBT::TAG_String){
301 foreach($lore as $t){
302 $this->lore[] = $t->getValue();
303 }
304 }
305 }
306
307 $this->removeEnchantments();
308 $enchantments = $tag->getListTag(self::TAG_ENCH);
309 if($enchantments !== null && $enchantments->getTagType() === NBT::TAG_Compound){
311 foreach($enchantments as $enchantment){
312 $magicNumber = $enchantment->getShort(self::TAG_ENCH_ID, -1);
313 $level = $enchantment->getShort(self::TAG_ENCH_LVL, 0);
314 if($level <= 0){
315 continue;
316 }
317 $type = EnchantmentIdMap::getInstance()->fromId($magicNumber);
318 if($type !== null){
319 $this->addEnchantment(new EnchantmentInstance($type, $level));
320 }
321 }
322 }
323
324 $this->blockEntityTag = $tag->getCompoundTag(self::TAG_BLOCK_ENTITY_TAG);
325
326 $this->canPlaceOn = [];
327 $canPlaceOn = $tag->getListTag(self::TAG_CAN_PLACE_ON);
328 if($canPlaceOn !== null && $canPlaceOn->getTagType() === NBT::TAG_String){
330 foreach($canPlaceOn as $entry){
331 $this->canPlaceOn[$entry->getValue()] = $entry->getValue();
332 }
333 }
334 $this->canDestroy = [];
335 $canDestroy = $tag->getListTag(self::TAG_CAN_DESTROY);
336 if($canDestroy !== null && $canDestroy->getTagType() === NBT::TAG_String){
338 foreach($canDestroy as $entry){
339 $this->canDestroy[$entry->getValue()] = $entry->getValue();
340 }
341 }
342
343 $this->keepOnDeath = $tag->getByte(self::TAG_KEEP_ON_DEATH, 0) !== 0;
344 }
345
346 protected function serializeCompoundTag(CompoundTag $tag) : void{
347 $display = $tag->getCompoundTag(self::TAG_DISPLAY);
348
349 if($this->customName !== ""){
350 $display ??= new CompoundTag();
351 $display->setString(self::TAG_DISPLAY_NAME, $this->customName);
352 }else{
353 $display?->removeTag(self::TAG_DISPLAY_NAME);
354 }
355
356 if(count($this->lore) > 0){
357 $loreTag = new ListTag();
358 foreach($this->lore as $line){
359 $loreTag->push(new StringTag($line));
360 }
361 $display ??= new CompoundTag();
362 $display->setTag(self::TAG_DISPLAY_LORE, $loreTag);
363 }else{
364 $display?->removeTag(self::TAG_DISPLAY_LORE);
365 }
366 $display !== null && $display->count() > 0 ?
367 $tag->setTag(self::TAG_DISPLAY, $display) :
368 $tag->removeTag(self::TAG_DISPLAY);
369
370 if(count($this->enchantments) > 0){
371 $ench = new ListTag();
372 $enchantmentIdMap = EnchantmentIdMap::getInstance();
373 foreach($this->enchantments as $enchantmentInstance){
374 $ench->push(CompoundTag::create()
375 ->setShort(self::TAG_ENCH_ID, $enchantmentIdMap->toId($enchantmentInstance->getType()))
376 ->setShort(self::TAG_ENCH_LVL, $enchantmentInstance->getLevel())
377 );
378 }
379 $tag->setTag(self::TAG_ENCH, $ench);
380 }else{
381 $tag->removeTag(self::TAG_ENCH);
382 }
383
384 $this->blockEntityTag !== null ?
385 $tag->setTag(self::TAG_BLOCK_ENTITY_TAG, clone $this->blockEntityTag) :
386 $tag->removeTag(self::TAG_BLOCK_ENTITY_TAG);
387
388 if(count($this->canPlaceOn) > 0){
389 $canPlaceOn = new ListTag();
390 foreach($this->canPlaceOn as $item){
391 $canPlaceOn->push(new StringTag($item));
392 }
393 $tag->setTag(self::TAG_CAN_PLACE_ON, $canPlaceOn);
394 }else{
395 $tag->removeTag(self::TAG_CAN_PLACE_ON);
396 }
397 if(count($this->canDestroy) > 0){
398 $canDestroy = new ListTag();
399 foreach($this->canDestroy as $item){
400 $canDestroy->push(new StringTag($item));
401 }
402 $tag->setTag(self::TAG_CAN_DESTROY, $canDestroy);
403 }else{
404 $tag->removeTag(self::TAG_CAN_DESTROY);
405 }
406
407 if($this->keepOnDeath){
408 $tag->setByte(self::TAG_KEEP_ON_DEATH, 1);
409 }else{
410 $tag->removeTag(self::TAG_KEEP_ON_DEATH);
411 }
412 }
413
414 public function getCount() : int{
415 return $this->count;
416 }
417
421 public function setCount(int $count) : Item{
422 $this->count = $count;
423
424 return $this;
425 }
426
433 public function pop(int $count = 1) : Item{
434 if($count > $this->count){
435 throw new \InvalidArgumentException("Cannot pop $count items from a stack of $this->count");
436 }
437
438 $item = clone $this;
439 $item->count = $count;
440
441 $this->count -= $count;
442
443 return $item;
444 }
445
446 public function isNull() : bool{
447 return $this->count <= 0;
448 }
449
453 final public function getName() : string{
454 return $this->hasCustomName() ? $this->getCustomName() : $this->getVanillaName();
455 }
456
460 public function getVanillaName() : string{
461 return $this->name;
462 }
463
473 public function getEnchantmentTags() : array{
474 return $this->enchantmentTags;
475 }
476
483 public function getEnchantability() : int{
484 return 1;
485 }
486
487 final public function canBePlaced() : bool{
488 return $this->getBlock()->canBePlaced();
489 }
490
491 public function getPlacementBlock(Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector) : Block{
492 return $this->getBlock($face);
493 }
494
498 public function getBlock(?int $clickedFace = null) : Block{
499 return VanillaBlocks::AIR();
500 }
501
502 final public function getTypeId() : int{
503 return $this->identifier->getTypeId();
504 }
505
506 final public function getStateId() : int{
507 return morton2d_encode($this->identifier->getTypeId(), $this->computeStateData());
508 }
509
510 private function computeStateData() : int{
511 $writer = new RuntimeDataWriter(16); //TODO: max bits should be a constant instead of being hardcoded all over the place
512 $this->describeState($writer);
513 return $writer->getValue();
514 }
515
520 protected function describeState(RuntimeDataDescriber $w) : void{
521 //NOOP
522 }
523
527 public function getMaxStackSize() : int{
528 return 64;
529 }
530
534 public function getFuelTime() : int{
535 return 0;
536 }
537
541 public function getFuelResidue() : Item{
542 $item = clone $this;
543 $item->pop();
544
545 return $item;
546 }
547
551 public function isFireProof() : bool{
552 return false;
553 }
554
558 public function getAttackPoints() : int{
559 return 1;
560 }
561
565 public function getDefensePoints() : int{
566 return 0;
567 }
568
573 public function getBlockToolType() : int{
574 return BlockToolType::NONE;
575 }
576
584 public function getBlockToolHarvestLevel() : int{
585 return 0;
586 }
587
588 public function getMiningEfficiency(bool $isCorrectTool) : float{
589 return 1;
590 }
591
597 public function onInteractBlock(Player $player, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, array &$returnedItems) : ItemUseResult{
598 return ItemUseResult::NONE;
599 }
600
607 public function onClickAir(Player $player, Vector3 $directionVector, array &$returnedItems) : ItemUseResult{
608 return ItemUseResult::NONE;
609 }
610
617 public function onReleaseUsing(Player $player, array &$returnedItems) : ItemUseResult{
618 return ItemUseResult::NONE;
619 }
620
626 public function onDestroyBlock(Block $block, array &$returnedItems) : bool{
627 return false;
628 }
629
635 public function onAttackEntity(Entity $victim, array &$returnedItems) : bool{
636 return false;
637 }
638
643 public function onTickWorn(Living $entity) : bool{
644 return false;
645 }
646
653 public function onInteractEntity(Player $player, Entity $entity, Vector3 $clickVector) : bool{
654 return false;
655 }
656
660 public function getCooldownTicks() : int{
661 return 0;
662 }
663
674 public function getCooldownTag() : ?string{
675 return null;
676 }
677
684 final public function equals(Item $item, bool $checkDamage = true, bool $checkCompound = true) : bool{
685 return $this->getStateId() === $item->getStateId() &&
686 (!$checkCompound || $this->getNamedTag()->equals($item->getNamedTag()));
687 }
688
692 final public function canStackWith(Item $other) : bool{
693 return $this->equals($other, true, true);
694 }
695
699 final public function equalsExact(Item $other) : bool{
700 return $this->canStackWith($other) && $this->count === $other->count;
701 }
702
703 final public function __toString() : string{
704 return "Item " . $this->name . " (" . $this->getTypeId() . ":" . $this->computeStateData() . ")x" . $this->count . ($this->hasNamedTag() ? " tags:0x" . base64_encode((new LittleEndianNbtSerializer())->write(new TreeRoot($this->getNamedTag()))) : "");
705 }
706
710 public function jsonSerialize() : array{
711 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.");
712 }
713
722 final public static function legacyJsonDeserialize(array $data) : Item{
723 $nbt = "";
724
725 //Backwards compatibility
726 if(isset($data["nbt"])){
727 $nbt = $data["nbt"];
728 }elseif(isset($data["nbt_hex"])){
729 $nbt = hex2bin($data["nbt_hex"]);
730 }elseif(isset($data["nbt_b64"])){
731 $nbt = base64_decode($data["nbt_b64"], true);
732 }
733
734 $itemStackData = GlobalItemDataHandlers::getUpgrader()->upgradeItemTypeDataInt(
735 (int) $data["id"],
736 (int) ($data["damage"] ?? 0),
737 (int) ($data["count"] ?? 1),
738 $nbt !== "" ? (new LittleEndianNbtSerializer())->read($nbt)->mustGetCompoundTag() : null
739 );
740
741 try{
742 return GlobalItemDataHandlers::getDeserializer()->deserializeStack($itemStackData);
743 }catch(ItemTypeDeserializeException $e){
744 throw new SavedDataLoadingException($e->getMessage(), 0, $e);
745 }
746 }
747
753 public function nbtSerialize(int $slot = -1) : CompoundTag{
754 return GlobalItemDataHandlers::getSerializer()->serializeStack($this, $slot !== -1 ? $slot : null)->toNbt();
755 }
756
761 public static function nbtDeserialize(CompoundTag $tag) : Item{
762 $itemData = GlobalItemDataHandlers::getUpgrader()->upgradeItemStackNbt($tag);
763 if($itemData === null){
764 return VanillaItems::AIR();
765 }
766
767 try{
768 return GlobalItemDataHandlers::getDeserializer()->deserializeStack($itemData);
769 }catch(ItemTypeDeserializeException $e){
770 throw new SavedDataLoadingException($e->getMessage(), 0, $e);
771 }
772 }
773
774 public function __clone(){
775 $this->nbt = clone $this->nbt;
776 if($this->blockEntityTag !== null){
777 $this->blockEntityTag = clone $this->blockEntityTag;
778 }
779 }
780}
static legacyJsonDeserialize(array $data)
Definition Item.php:722
getBlock(?int $clickedFace=null)
Definition Item.php:498
describeState(RuntimeDataDescriber $w)
Definition Item.php:520
nbtSerialize(int $slot=-1)
Definition Item.php:753
onAttackEntity(Entity $victim, array &$returnedItems)
Definition Item.php:635
setCustomBlockData(CompoundTag $compound)
Definition Item.php:136
canStackWith(Item $other)
Definition Item.php:692
equals(Item $item, bool $checkDamage=true, bool $checkCompound=true)
Definition Item.php:684
deserializeCompoundTag(CompoundTag $tag)
Definition Item.php:291
CompoundTag $blockEntityTag
Definition Item.php:88
pop(int $count=1)
Definition Item.php:433
onReleaseUsing(Player $player, array &$returnedItems)
Definition Item.php:617
setNamedTag(CompoundTag $tag)
Definition Item.php:266
static nbtDeserialize(CompoundTag $tag)
Definition Item.php:761
setCount(int $count)
Definition Item.php:421
setLore(array $lines)
Definition Item.php:183
onClickAir(Player $player, Vector3 $directionVector, array &$returnedItems)
Definition Item.php:607
setCanPlaceOn(array $canPlaceOn)
Definition Item.php:205
onTickWorn(Living $entity)
Definition Item.php:643
__construct(private ItemIdentifier $identifier, protected string $name="Unknown", private array $enchantmentTags=[])
Definition Item.php:113
onInteractBlock(Player $player, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, array &$returnedItems)
Definition Item.php:597
setCanDestroy(array $canDestroy)
Definition Item.php:224
onDestroyBlock(Block $block, array &$returnedItems)
Definition Item.php:626
setCustomName(string $name)
Definition Item.php:157
equalsExact(Item $other)
Definition Item.php:699
onInteractEntity(Player $player, Entity $entity, Vector3 $clickVector)
Definition Item.php:653