diff --git a/src/network/mcpe/convert/ItemTranslator.php b/src/network/mcpe/convert/ItemTranslator.php index d1a7c3a58..1af9adba2 100644 --- a/src/network/mcpe/convert/ItemTranslator.php +++ b/src/network/mcpe/convert/ItemTranslator.php @@ -142,10 +142,10 @@ final class ItemTranslator{ } /** - * @return int[] - * @phpstan-return array{int, int} + * @return int[]|null + * @phpstan-return array{int, int}|null */ - public function toNetworkId(int $internalId, int $internalMeta) : array{ + public function toNetworkIdQuiet(int $internalId, int $internalMeta) : ?array{ if($internalMeta === -1){ $internalMeta = 0x7fff; } @@ -156,7 +156,16 @@ final class ItemTranslator{ return [$this->simpleCoreToNetMapping[$internalId], $internalMeta]; } - throw new \InvalidArgumentException("Unmapped ID/metadata combination $internalId:$internalMeta"); + return null; + } + + /** + * @return int[] + * @phpstan-return array{int, int} + */ + public function toNetworkId(int $internalId, int $internalMeta) : array{ + return $this->toNetworkIdQuiet($internalId, $internalMeta) ?? + throw new \InvalidArgumentException("Unmapped ID/metadata combination $internalId:$internalMeta"); } /** diff --git a/src/network/mcpe/convert/TypeConverter.php b/src/network/mcpe/convert/TypeConverter.php index d878423f6..21b3ab749 100644 --- a/src/network/mcpe/convert/TypeConverter.php +++ b/src/network/mcpe/convert/TypeConverter.php @@ -58,6 +58,7 @@ class TypeConverter{ private const DAMAGE_TAG = "Damage"; //TAG_Int private const DAMAGE_TAG_CONFLICT_RESOLUTION = "___Damage_ProtocolCollisionResolution___"; + private const PM_ID_TAG = "___Id___"; private const PM_META_TAG = "___Meta___"; /** @var int */ @@ -143,26 +144,40 @@ class TypeConverter{ } $isBlockItem = $itemStack->getId() < 256; - if($itemStack instanceof Durable and $itemStack->getDamage() > 0){ - if($nbt !== null){ - if(($existing = $nbt->getTag(self::DAMAGE_TAG)) !== null){ - $nbt->removeTag(self::DAMAGE_TAG); - $nbt->setTag(self::DAMAGE_TAG_CONFLICT_RESOLUTION, $existing); - } - }else{ - $nbt = new CompoundTag(); - } - $nbt->setInt(self::DAMAGE_TAG, $itemStack->getDamage()); - }elseif($isBlockItem && $itemStack->getMeta() !== 0){ - //TODO HACK: This foul-smelling code ensures that we can correctly deserialize an item when the - //client sends it back to us, because as of 1.16.220, blockitems quietly discard their metadata - //client-side. Aside from being very annoying, this also breaks various server-side behaviours. + + $idMeta = ItemTranslator::getInstance()->toNetworkIdQuiet($itemStack->getId(), $itemStack->getMeta()); + if($idMeta === null){ + //Display unmapped items as INFO_UPDATE, but stick something in their NBT to make sure they don't stack with + //other unmapped items. + [$id, $meta] = ItemTranslator::getInstance()->toNetworkId(ItemIds::INFO_UPDATE, 0); if($nbt === null){ $nbt = new CompoundTag(); } + $nbt->setInt(self::PM_ID_TAG, $itemStack->getId()); $nbt->setInt(self::PM_META_TAG, $itemStack->getMeta()); + }else{ + [$id, $meta] = $idMeta; + + if($itemStack instanceof Durable and $itemStack->getDamage() > 0){ + if($nbt !== null){ + if(($existing = $nbt->getTag(self::DAMAGE_TAG)) !== null){ + $nbt->removeTag(self::DAMAGE_TAG); + $nbt->setTag(self::DAMAGE_TAG_CONFLICT_RESOLUTION, $existing); + } + }else{ + $nbt = new CompoundTag(); + } + $nbt->setInt(self::DAMAGE_TAG, $itemStack->getDamage()); + }elseif($isBlockItem && $itemStack->getMeta() !== 0){ + //TODO HACK: This foul-smelling code ensures that we can correctly deserialize an item when the + //client sends it back to us, because as of 1.16.220, blockitems quietly discard their metadata + //client-side. Aside from being very annoying, this also breaks various server-side behaviours. + if($nbt === null){ + $nbt = new CompoundTag(); + } + $nbt->setInt(self::PM_META_TAG, $itemStack->getMeta()); + } } - [$id, $meta] = ItemTranslator::getInstance()->toNetworkId($itemStack->getId(), $itemStack->getMeta()); $blockRuntimeId = 0; if($isBlockItem){ @@ -197,6 +212,13 @@ class TypeConverter{ if($compound !== null){ $compound = clone $compound; + if(($idTag = $compound->getTag(self::PM_ID_TAG)) instanceof IntTag){ + $id = $idTag->getValue(); + $compound->removeTag(self::PM_ID_TAG); + if($compound->count() === 0){ + $compound = null; + } + } if(($damageTag = $compound->getTag(self::DAMAGE_TAG)) instanceof IntTag){ $meta = $damageTag->getValue(); $compound->removeTag(self::DAMAGE_TAG);