PocketMine-MP 5.33.2 git-09cc76ae2b49f1fe3ab0253e6e987fb82bd0a08f
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
53use function base64_decode;
54use function base64_encode;
55use function count;
56use function gettype;
57use function hex2bin;
58use function is_string;
59use function morton2d_encode;
60
61class Item implements \JsonSerializable{
63
64 public const TAG_ENCH = "ench";
65 private const TAG_ENCH_ID = "id"; //TAG_Short
66 private const TAG_ENCH_LVL = "lvl"; //TAG_Short
67
68 public const TAG_DISPLAY = "display";
69 public const TAG_BLOCK_ENTITY_TAG = "BlockEntityTag";
70
71 public const TAG_DISPLAY_NAME = "Name";
72 public const TAG_DISPLAY_LORE = "Lore";
73
74 public const TAG_KEEP_ON_DEATH = "minecraft:keep_on_death";
75
76 private const TAG_CAN_PLACE_ON = "CanPlaceOn"; //TAG_List<TAG_String>
77 private const TAG_CAN_DESTROY = "CanDestroy"; //TAG_List<TAG_String>
78
79 private CompoundTag $nbt;
80
81 protected int $count = 1;
82
83 //TODO: this stuff should be moved to itemstack properties, not mushed in with type properties
84
85 protected string $customName = "";
87 protected array $lore = [];
89 protected ?CompoundTag $blockEntityTag = null;
90
95 protected array $canPlaceOn = [];
100 protected array $canDestroy = [];
101
102 protected bool $keepOnDeath = false;
103
114 public function __construct(
115 private ItemIdentifier $identifier,
116 protected string $name = "Unknown",
117 private array $enchantmentTags = []
118 ){
119 $this->nbt = new CompoundTag();
120 }
121
122 public function hasCustomBlockData() : bool{
123 return $this->blockEntityTag !== null;
124 }
125
129 public function clearCustomBlockData(){
130 $this->blockEntityTag = null;
131 return $this;
132 }
133
137 public function setCustomBlockData(CompoundTag $compound) : Item{
138 $this->blockEntityTag = clone $compound;
139
140 return $this;
141 }
142
143 public function getCustomBlockData() : ?CompoundTag{
144 return $this->blockEntityTag;
145 }
146
147 public function hasCustomName() : bool{
148 return $this->customName !== "";
149 }
150
151 public function getCustomName() : string{
152 return $this->customName;
153 }
154
158 public function setCustomName(string $name) : Item{
159 Utils::checkUTF8($name);
160 $this->customName = $name;
161 return $this;
162 }
163
167 public function clearCustomName() : Item{
168 $this->setCustomName("");
169 return $this;
170 }
171
175 public function getLore() : array{
176 return $this->lore;
177 }
178
184 public function setLore(array $lines) : Item{
185 foreach($lines as $line){
186 if(!is_string($line)){
187 throw new \TypeError("Expected string[], but found " . gettype($line) . " in given array");
188 }
189 Utils::checkUTF8($line);
190 }
191 $this->lore = $lines;
192 return $this;
193 }
194
199 public function getCanPlaceOn() : array{
200 return $this->canPlaceOn;
201 }
202
206 public function setCanPlaceOn(array $canPlaceOn) : void{
207 $this->canPlaceOn = [];
208 foreach($canPlaceOn as $value){
209 $this->canPlaceOn[$value] = $value;
210 }
211 }
212
217 public function getCanDestroy() : array{
218 return $this->canDestroy;
219 }
220
224 public function setCanDestroy(array $canDestroy) : void{
225 $this->canDestroy = [];
226 foreach($canDestroy as $value){
227 $this->canDestroy[$value] = $value;
228 }
229 }
230
234 public function keepOnDeath() : bool{
235 return $this->keepOnDeath;
236 }
237
238 public function setKeepOnDeath(bool $keepOnDeath) : void{
239 $this->keepOnDeath = $keepOnDeath;
240 }
241
245 public function hasNamedTag() : bool{
246 return $this->getNamedTag()->count() > 0;
247 }
248
253 public function getNamedTag() : CompoundTag{
254 $this->serializeCompoundTag($this->nbt);
255 return $this->nbt;
256 }
257
264 public function setNamedTag(CompoundTag $tag) : Item{
265 if($tag->getCount() === 0){
266 return $this->clearNamedTag();
267 }
268
269 $this->nbt = clone $tag;
270 $this->deserializeCompoundTag($this->nbt);
271
272 return $this;
273 }
274
280 public function clearNamedTag() : Item{
281 $this->nbt = new CompoundTag();
282 $this->deserializeCompoundTag($this->nbt);
283 return $this;
284 }
285
289 protected function deserializeCompoundTag(CompoundTag $tag) : void{
290 $this->customName = "";
291 $this->lore = [];
292
293 $display = $tag->getCompoundTag(self::TAG_DISPLAY);
294 if($display !== null){
295 $this->customName = $display->getString(self::TAG_DISPLAY_NAME, $this->customName);
296 $lore = $display->getListTag(self::TAG_DISPLAY_LORE);
297 if($lore !== null && $lore->getTagType() === NBT::TAG_String){
299 foreach($lore as $t){
300 $this->lore[] = $t->getValue();
301 }
302 }
303 }
304
305 $this->removeEnchantments();
306 $enchantments = $tag->getListTag(self::TAG_ENCH);
307 if($enchantments !== null && $enchantments->getTagType() === NBT::TAG_Compound){
309 foreach($enchantments as $enchantment){
310 $magicNumber = $enchantment->getShort(self::TAG_ENCH_ID, -1);
311 $level = $enchantment->getShort(self::TAG_ENCH_LVL, 0);
312 if($level <= 0){
313 continue;
314 }
315 $type = EnchantmentIdMap::getInstance()->fromId($magicNumber);
316 if($type !== null){
317 $this->addEnchantment(new EnchantmentInstance($type, $level));
318 }
319 }
320 }
321
322 $this->blockEntityTag = $tag->getCompoundTag(self::TAG_BLOCK_ENTITY_TAG);
323
324 $this->canPlaceOn = [];
325 $canPlaceOn = $tag->getListTag(self::TAG_CAN_PLACE_ON);
326 if($canPlaceOn !== null && $canPlaceOn->getTagType() === NBT::TAG_String){
328 foreach($canPlaceOn as $entry){
329 $this->canPlaceOn[$entry->getValue()] = $entry->getValue();
330 }
331 }
332 $this->canDestroy = [];
333 $canDestroy = $tag->getListTag(self::TAG_CAN_DESTROY);
334 if($canDestroy !== null && $canDestroy->getTagType() === NBT::TAG_String){
336 foreach($canDestroy as $entry){
337 $this->canDestroy[$entry->getValue()] = $entry->getValue();
338 }
339 }
340
341 $this->keepOnDeath = $tag->getByte(self::TAG_KEEP_ON_DEATH, 0) !== 0;
342 }
343
344 protected function serializeCompoundTag(CompoundTag $tag) : void{
345 $display = $tag->getCompoundTag(self::TAG_DISPLAY);
346
347 if($this->customName !== ""){
348 $display ??= new CompoundTag();
349 $display->setString(self::TAG_DISPLAY_NAME, $this->customName);
350 }else{
351 $display?->removeTag(self::TAG_DISPLAY_NAME);
352 }
353
354 if(count($this->lore) > 0){
355 $loreTag = new ListTag();
356 foreach($this->lore as $line){
357 $loreTag->push(new StringTag($line));
358 }
359 $display ??= new CompoundTag();
360 $display->setTag(self::TAG_DISPLAY_LORE, $loreTag);
361 }else{
362 $display?->removeTag(self::TAG_DISPLAY_LORE);
363 }
364 $display !== null && $display->count() > 0 ?
365 $tag->setTag(self::TAG_DISPLAY, $display) :
366 $tag->removeTag(self::TAG_DISPLAY);
367
368 if(count($this->enchantments) > 0){
369 $ench = new ListTag();
370 $enchantmentIdMap = EnchantmentIdMap::getInstance();
371 foreach($this->enchantments as $enchantmentInstance){
372 $ench->push(CompoundTag::create()
373 ->setShort(self::TAG_ENCH_ID, $enchantmentIdMap->toId($enchantmentInstance->getType()))
374 ->setShort(self::TAG_ENCH_LVL, $enchantmentInstance->getLevel())
375 );
376 }
377 $tag->setTag(self::TAG_ENCH, $ench);
378 }else{
379 $tag->removeTag(self::TAG_ENCH);
380 }
381
382 $this->blockEntityTag !== null ?
383 $tag->setTag(self::TAG_BLOCK_ENTITY_TAG, clone $this->blockEntityTag) :
384 $tag->removeTag(self::TAG_BLOCK_ENTITY_TAG);
385
386 if(count($this->canPlaceOn) > 0){
387 $canPlaceOn = new ListTag();
388 foreach($this->canPlaceOn as $item){
389 $canPlaceOn->push(new StringTag($item));
390 }
391 $tag->setTag(self::TAG_CAN_PLACE_ON, $canPlaceOn);
392 }else{
393 $tag->removeTag(self::TAG_CAN_PLACE_ON);
394 }
395 if(count($this->canDestroy) > 0){
396 $canDestroy = new ListTag();
397 foreach($this->canDestroy as $item){
398 $canDestroy->push(new StringTag($item));
399 }
400 $tag->setTag(self::TAG_CAN_DESTROY, $canDestroy);
401 }else{
402 $tag->removeTag(self::TAG_CAN_DESTROY);
403 }
404
405 if($this->keepOnDeath){
406 $tag->setByte(self::TAG_KEEP_ON_DEATH, 1);
407 }else{
408 $tag->removeTag(self::TAG_KEEP_ON_DEATH);
409 }
410 }
411
412 public function getCount() : int{
413 return $this->count;
414 }
415
419 public function setCount(int $count) : Item{
420 $this->count = $count;
421
422 return $this;
423 }
424
431 public function pop(int $count = 1) : Item{
432 if($count > $this->count){
433 throw new \InvalidArgumentException("Cannot pop $count items from a stack of $this->count");
434 }
435
436 $item = clone $this;
437 $item->count = $count;
438
439 $this->count -= $count;
440
441 return $item;
442 }
443
444 public function isNull() : bool{
445 return $this->count <= 0;
446 }
447
451 final public function getName() : string{
452 return $this->hasCustomName() ? $this->getCustomName() : $this->getVanillaName();
453 }
454
458 public function getVanillaName() : string{
459 return $this->name;
460 }
461
471 public function getEnchantmentTags() : array{
472 return $this->enchantmentTags;
473 }
474
481 public function getEnchantability() : int{
482 return 1;
483 }
484
485 final public function canBePlaced() : bool{
486 return $this->getBlock()->canBePlaced();
487 }
488
489 protected final function tryPlacementTransaction(Block $blockPlace, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player) : ?BlockTransaction{
490 $position = $blockReplace->getPosition();
491 $blockPlace->position($position->getWorld(), $position->getFloorX(), $position->getFloorY(), $position->getFloorZ());
492 if(!$blockPlace->canBePlacedAt($blockReplace, $clickVector, $face, $blockReplace->getPosition()->equals($blockClicked->getPosition()))){
493 return null;
494 }
495 $transaction = new BlockTransaction($position->getWorld());
496 return $blockPlace->place($transaction, $this, $blockReplace, $blockClicked, $face, $clickVector, $player) ? $transaction : null;
497 }
498
499 public function getPlacementTransaction(Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : ?BlockTransaction{
500 return $this->tryPlacementTransaction($this->getBlock($face), $blockReplace, $blockClicked, $face, $clickVector, $player);
501 }
502
506 public function getBlock(?int $clickedFace = null) : Block{
507 return VanillaBlocks::AIR();
508 }
509
510 final public function getTypeId() : int{
511 return $this->identifier->getTypeId();
512 }
513
514 final public function getStateId() : int{
515 return morton2d_encode($this->identifier->getTypeId(), $this->computeStateData());
516 }
517
518 private function computeStateData() : int{
519 $writer = new RuntimeDataWriter(16); //TODO: max bits should be a constant instead of being hardcoded all over the place
520 $this->describeState($writer);
521 return $writer->getValue();
522 }
523
528 protected function describeState(RuntimeDataDescriber $w) : void{
529 //NOOP
530 }
531
535 public function getMaxStackSize() : int{
536 return 64;
537 }
538
542 public function getFuelTime() : int{
543 return 0;
544 }
545
549 public function getFuelResidue() : Item{
550 $item = clone $this;
551 $item->pop();
552
553 return $item;
554 }
555
559 public function isFireProof() : bool{
560 return false;
561 }
562
566 public function getAttackPoints() : int{
567 return 1;
568 }
569
573 public function getDefensePoints() : int{
574 return 0;
575 }
576
581 public function getBlockToolType() : int{
582 return BlockToolType::NONE;
583 }
584
592 public function getBlockToolHarvestLevel() : int{
593 return 0;
594 }
595
596 public function getMiningEfficiency(bool $isCorrectTool) : float{
597 return 1;
598 }
599
605 public function onInteractBlock(Player $player, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, array &$returnedItems) : ItemUseResult{
606 return ItemUseResult::NONE;
607 }
608
615 public function onClickAir(Player $player, Vector3 $directionVector, array &$returnedItems) : ItemUseResult{
616 return ItemUseResult::NONE;
617 }
618
625 public function onReleaseUsing(Player $player, array &$returnedItems) : ItemUseResult{
626 return ItemUseResult::NONE;
627 }
628
634 public function onDestroyBlock(Block $block, array &$returnedItems) : bool{
635 return false;
636 }
637
643 public function onAttackEntity(Entity $victim, array &$returnedItems) : bool{
644 return false;
645 }
646
651 public function onTickWorn(Living $entity) : bool{
652 return false;
653 }
654
661 public function onInteractEntity(Player $player, Entity $entity, Vector3 $clickVector) : bool{
662 return false;
663 }
664
668 public function getCooldownTicks() : int{
669 return 0;
670 }
671
682 public function getCooldownTag() : ?string{
683 return null;
684 }
685
692 final public function equals(Item $item, bool $checkDamage = true, bool $checkCompound = true) : bool{
693 return $this->getStateId() === $item->getStateId() &&
694 (!$checkCompound || $this->getNamedTag()->equals($item->getNamedTag()));
695 }
696
700 final public function canStackWith(Item $other) : bool{
701 return $this->equals($other, true, true);
702 }
703
707 final public function equalsExact(Item $other) : bool{
708 return $this->canStackWith($other) && $this->count === $other->count;
709 }
710
711 final public function __toString() : string{
712 return "Item " . $this->name . " (" . $this->getTypeId() . ":" . $this->computeStateData() . ")x" . $this->count . ($this->hasNamedTag() ? " tags:0x" . base64_encode((new LittleEndianNbtSerializer())->write(new TreeRoot($this->getNamedTag()))) : "");
713 }
714
718 public function jsonSerialize() : array{
719 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.");
720 }
721
730 final public static function legacyJsonDeserialize(array $data) : Item{
731 $nbt = "";
732
733 //Backwards compatibility
734 if(isset($data["nbt"])){
735 $nbt = $data["nbt"];
736 }elseif(isset($data["nbt_hex"])){
737 $nbt = hex2bin($data["nbt_hex"]);
738 }elseif(isset($data["nbt_b64"])){
739 $nbt = base64_decode($data["nbt_b64"], true);
740 }
741
742 $itemStackData = GlobalItemDataHandlers::getUpgrader()->upgradeItemTypeDataInt(
743 (int) $data["id"],
744 (int) ($data["damage"] ?? 0),
745 (int) ($data["count"] ?? 1),
746 $nbt !== "" ? (new LittleEndianNbtSerializer())->read($nbt)->mustGetCompoundTag() : null
747 );
748
749 try{
750 return GlobalItemDataHandlers::getDeserializer()->deserializeStack($itemStackData);
751 }catch(ItemTypeDeserializeException $e){
752 throw new SavedDataLoadingException($e->getMessage(), 0, $e);
753 }
754 }
755
761 public function nbtSerialize(int $slot = -1) : CompoundTag{
762 return GlobalItemDataHandlers::getSerializer()->serializeStack($this, $slot !== -1 ? $slot : null)->toNbt();
763 }
764
769 public static function nbtDeserialize(CompoundTag $tag) : Item{
770 $itemData = GlobalItemDataHandlers::getUpgrader()->upgradeItemStackNbt($tag);
771 if($itemData === null){
772 return VanillaItems::AIR();
773 }
774
775 try{
776 return GlobalItemDataHandlers::getDeserializer()->deserializeStack($itemData);
777 }catch(ItemTypeDeserializeException $e){
778 throw new SavedDataLoadingException($e->getMessage(), 0, $e);
779 }
780 }
781
782 public function __clone(){
783 $this->nbt = clone $this->nbt;
784 if($this->blockEntityTag !== null){
785 $this->blockEntityTag = clone $this->blockEntityTag;
786 }
787 }
788}
static legacyJsonDeserialize(array $data)
Definition Item.php:730
getBlock(?int $clickedFace=null)
Definition Item.php:506
describeState(RuntimeDataDescriber $w)
Definition Item.php:528
nbtSerialize(int $slot=-1)
Definition Item.php:761
onAttackEntity(Entity $victim, array &$returnedItems)
Definition Item.php:643
setCustomBlockData(CompoundTag $compound)
Definition Item.php:137
canStackWith(Item $other)
Definition Item.php:700
equals(Item $item, bool $checkDamage=true, bool $checkCompound=true)
Definition Item.php:692
deserializeCompoundTag(CompoundTag $tag)
Definition Item.php:289
CompoundTag $blockEntityTag
Definition Item.php:89
pop(int $count=1)
Definition Item.php:431
onReleaseUsing(Player $player, array &$returnedItems)
Definition Item.php:625
setNamedTag(CompoundTag $tag)
Definition Item.php:264
static nbtDeserialize(CompoundTag $tag)
Definition Item.php:769
setCount(int $count)
Definition Item.php:419
setLore(array $lines)
Definition Item.php:184
onClickAir(Player $player, Vector3 $directionVector, array &$returnedItems)
Definition Item.php:615
setCanPlaceOn(array $canPlaceOn)
Definition Item.php:206
onTickWorn(Living $entity)
Definition Item.php:651
__construct(private ItemIdentifier $identifier, protected string $name="Unknown", private array $enchantmentTags=[])
Definition Item.php:114
onInteractBlock(Player $player, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, array &$returnedItems)
Definition Item.php:605
setCanDestroy(array $canDestroy)
Definition Item.php:224
onDestroyBlock(Block $block, array &$returnedItems)
Definition Item.php:634
setCustomName(string $name)
Definition Item.php:158
equalsExact(Item $other)
Definition Item.php:707
onInteractEntity(Player $player, Entity $entity, Vector3 $clickVector)
Definition Item.php:661