PocketMine-MP 5.35.1 git-e32e836dad793a3a3c8ddd8927c00e112b1e576a
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, StringTag::class);
301 if($lore !== null){
302 foreach($lore as $t){
303 $this->lore[] = $t->getValue();
304 }
305 }
306 }
307
308 $this->removeEnchantments();
309 $enchantments = $tag->getListTag(self::TAG_ENCH, CompoundTag::class);
310 if($enchantments !== null){
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, StringTag::class);
328 if($canPlaceOn !== null){
329 foreach($canPlaceOn as $entry){
330 $this->canPlaceOn[$entry->getValue()] = $entry->getValue();
331 }
332 }
333 $this->canDestroy = [];
334 $canDestroy = $tag->getListTag(self::TAG_CAN_DESTROY, StringTag::class);
335 if($canDestroy !== null){
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, Facing $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, Facing $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(?Facing $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, Facing $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
788 public static function safeNbtDeserialize(CompoundTag $tag, string $errorLogContext, ?\Logger $logger = null) : Item{
789 try{
790 return self::nbtDeserialize($tag);
791 }catch(SavedDataLoadingException $e){
792 //TODO: what if the intention was to suppress logging?
793 $logger ??= \GlobalLogger::get();
794 $logger->error("$errorLogContext: Error deserializing item (item will be replaced by AIR): " . $e->getMessage());
795 //no trace here, otherwise things could get very noisy
796 return VanillaItems::AIR();
797 }
798 }
799
800 public function __clone(){
801 $this->nbt = clone $this->nbt;
802 if($this->blockEntityTag !== null){
803 $this->blockEntityTag = clone $this->blockEntityTag;
804 }
805 }
806}
static legacyJsonDeserialize(array $data)
Definition Item.php:730
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:138
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:293
CompoundTag $blockEntityTag
Definition Item.php:90
pop(int $count=1)
Definition Item.php:431
onReleaseUsing(Player $player, array &$returnedItems)
Definition Item.php:625
setNamedTag(CompoundTag $tag)
Definition Item.php:268
static nbtDeserialize(CompoundTag $tag)
Definition Item.php:769
setCount(int $count)
Definition Item.php:419
setLore(array $lines)
Definition Item.php:185
onClickAir(Player $player, Vector3 $directionVector, array &$returnedItems)
Definition Item.php:615
setCanPlaceOn(array $canPlaceOn)
Definition Item.php:207
getBlock(?Facing $clickedFace=null)
Definition Item.php:506
onTickWorn(Living $entity)
Definition Item.php:651
__construct(private ItemIdentifier $identifier, protected string $name="Unknown", private array $enchantmentTags=[])
Definition Item.php:115
setCanDestroy(array $canDestroy)
Definition Item.php:226
static safeNbtDeserialize(CompoundTag $tag, string $errorLogContext, ?\Logger $logger=null)
Definition Item.php:788
onDestroyBlock(Block $block, array &$returnedItems)
Definition Item.php:634
setCustomName(string $name)
Definition Item.php:159
onInteractBlock(Player $player, Block $blockReplace, Block $blockClicked, Facing $face, Vector3 $clickVector, array &$returnedItems)
Definition Item.php:605
equalsExact(Item $other)
Definition Item.php:707
onInteractEntity(Player $player, Entity $entity, Vector3 $clickVector)
Definition Item.php:661
getListTag(string $name, string $tagClass=Tag::class)