Merge branch 'stable'

This commit is contained in:
Dylan K. Taylor 2021-04-07 21:25:39 +01:00
commit 1e6d97a157
No known key found for this signature in database
GPG Key ID: 8927471A91CAFD3D
29 changed files with 259 additions and 170 deletions

17
changelogs/3.19.md Normal file
View File

@ -0,0 +1,17 @@
**For Minecraft: Bedrock Edition 1.16.220**
### Note about API versions
Plugins which don't touch the protocol and compatible with any previous 3.x.y version will also run on these releases and do not need API bumps.
Plugin developers should **only** update their required API to this version if you need the changes in this build.
**WARNING: If your plugin uses the protocol, you're not shielded by API change constraints.** You should consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you do.
# 3.19.0
- Added support for Minecraft: Bedrock Edition 1.16.220.
- Removed compatibility with earlier versions.
## Known issues (please don't open issues for these)
- Walls don't connect to each other
- Pumpkin and melon stems may not connect to their corresponding pumpkin/melon
- New blocks, items & mobs aren't implemented
- Nether doesn't exist

View File

@ -52,6 +52,7 @@ use pocketmine\network\mcpe\protocol\PlayerSkinPacket;
use pocketmine\network\mcpe\protocol\types\entity\EntityIds;
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataProperties;
use pocketmine\network\mcpe\protocol\types\entity\StringMetadataProperty;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStackWrapper;
use pocketmine\network\mcpe\protocol\types\PlayerListEntry;
use pocketmine\player\Player;
use pocketmine\utils\Limits;
@ -428,7 +429,7 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
$pk->motion = $this->getMotion();
$pk->yaw = $this->location->yaw;
$pk->pitch = $this->location->pitch;
$pk->item = TypeConverter::getInstance()->coreItemStackToNet($this->getInventory()->getItemInHand());
$pk->item = ItemStackWrapper::legacy(TypeConverter::getInstance()->coreItemStackToNet($this->getInventory()->getItemInHand()));
$pk->metadata = $this->getAllNetworkData();
$player->getNetworkSession()->sendDataPacket($pk);

View File

@ -35,6 +35,7 @@ use pocketmine\nbt\tag\CompoundTag;
use pocketmine\network\mcpe\convert\TypeConverter;
use pocketmine\network\mcpe\protocol\AddItemActorPacket;
use pocketmine\network\mcpe\protocol\types\entity\EntityIds;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStackWrapper;
use pocketmine\player\Player;
use function max;
@ -209,7 +210,7 @@ class ItemEntity extends Entity{
$pk->entityRuntimeId = $this->getId();
$pk->position = $this->location->asVector3();
$pk->motion = $this->getMotion();
$pk->item = TypeConverter::getInstance()->coreItemStackToNet($this->getItem());
$pk->item = ItemStackWrapper::legacy(TypeConverter::getInstance()->coreItemStackToNet($this->getItem()));
$pk->metadata = $this->getAllNetworkData();
$player->getNetworkSession()->sendDataPacket($pk);

View File

@ -234,7 +234,7 @@ class InventoryManager{
if($selected !== $this->clientSelectedHotbarSlot){
$this->session->sendDataPacket(MobEquipmentPacket::create(
$this->player->getId(),
TypeConverter::getInstance()->coreItemStackToNet($this->player->getInventory()->getItemInHand()),
ItemStackWrapper::legacy(TypeConverter::getInstance()->coreItemStackToNet($this->player->getInventory()->getItemInHand())),
$selected,
ContainerIds::INVENTORY
));

View File

@ -91,6 +91,7 @@ use pocketmine\network\mcpe\protocol\types\DimensionIds;
use pocketmine\network\mcpe\protocol\types\entity\Attribute as NetworkAttribute;
use pocketmine\network\mcpe\protocol\types\entity\MetadataProperty;
use pocketmine\network\mcpe\protocol\types\inventory\ContainerIds;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStackWrapper;
use pocketmine\network\mcpe\protocol\types\PlayerListEntry;
use pocketmine\network\mcpe\protocol\types\PlayerPermissions;
use pocketmine\network\mcpe\protocol\UpdateAttributesPacket;
@ -958,7 +959,7 @@ class NetworkSession{
public function onMobEquipmentChange(Human $mob) : void{
//TODO: we could send zero for slot here because remote players don't need to know which slot was selected
$inv = $mob->getInventory();
$this->sendDataPacket(MobEquipmentPacket::create($mob->getId(), TypeConverter::getInstance()->coreItemStackToNet($inv->getItemInHand()), $inv->getHeldItemIndex(), ContainerIds::INVENTORY));
$this->sendDataPacket(MobEquipmentPacket::create($mob->getId(), ItemStackWrapper::legacy(TypeConverter::getInstance()->coreItemStackToNet($inv->getItemInHand())), $inv->getHeldItemIndex(), ContainerIds::INVENTORY));
}
public function onMobArmorChange(Living $mob) : void{
@ -966,10 +967,10 @@ class NetworkSession{
$converter = TypeConverter::getInstance();
$this->sendDataPacket(MobArmorEquipmentPacket::create(
$mob->getId(),
$converter->coreItemStackToNet($inv->getHelmet()),
$converter->coreItemStackToNet($inv->getChestplate()),
$converter->coreItemStackToNet($inv->getLeggings()),
$converter->coreItemStackToNet($inv->getBoots())
ItemStackWrapper::legacy($converter->coreItemStackToNet($inv->getHelmet())),
ItemStackWrapper::legacy($converter->coreItemStackToNet($inv->getChestplate())),
ItemStackWrapper::legacy($converter->coreItemStackToNet($inv->getLeggings())),
ItemStackWrapper::legacy($converter->coreItemStackToNet($inv->getBoots()))
));
}

View File

@ -22,6 +22,7 @@ declare(strict_types=1);
namespace pocketmine\network\mcpe\convert;
use pocketmine\block\BlockLegacyIds;
use pocketmine\block\inventory\AnvilInventory;
use pocketmine\block\inventory\EnchantInventory;
use pocketmine\crafting\CraftingGrid;
@ -54,6 +55,7 @@ class TypeConverter{
private const DAMAGE_TAG = "Damage"; //TAG_Int
private const DAMAGE_TAG_CONFLICT_RESOLUTION = "___Damage_ProtocolCollisionResolution___";
private const PM_META_TAG = "___Meta___";
/** @var int */
private $shieldRuntimeId;
@ -136,6 +138,8 @@ class TypeConverter{
if($itemStack->hasNamedTag()){
$nbt = clone $itemStack->getNamedTag();
}
$block = $itemStack->getBlock();
if($itemStack instanceof Durable and $itemStack->getDamage() > 0){
if($nbt !== null){
if(($existing = $nbt->getTag(self::DAMAGE_TAG)) !== null){
@ -146,13 +150,24 @@ class TypeConverter{
$nbt = new CompoundTag();
}
$nbt->setInt(self::DAMAGE_TAG, $itemStack->getDamage());
}elseif($block->getId() !== BlockLegacyIds::AIR && $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 = $block->getId() === BlockLegacyIds::AIR ? 0 : RuntimeBlockMapping::getInstance()->toRuntimeId($block->getFullId());
return new ItemStack(
$id,
$meta,
$itemStack->getCount(),
$blockRuntimeId,
$nbt,
[],
[],
@ -179,6 +194,15 @@ class TypeConverter{
}elseif($compound->count() === 0){
$compound = null;
}
}elseif(($metaTag = $compound->getTag(self::PM_META_TAG)) instanceof IntTag){
//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.
$meta = $metaTag->getValue();
$compound->removeTag(self::PM_META_TAG);
if($compound->count() === 0){
$compound = null;
}
}
}
@ -210,12 +234,12 @@ class TypeConverter{
* @throws \UnexpectedValueException
*/
public function createInventoryAction(NetworkInventoryAction $action, Player $player) : ?InventoryAction{
if($action->oldItem->equals($action->newItem)){
if($action->oldItem->getItemStack()->equals($action->newItem->getItemStack())){
//filter out useless noise in 1.13
return null;
}
$old = $this->netItemStackToCore($action->oldItem);
$new = $this->netItemStackToCore($action->newItem);
$old = $this->netItemStackToCore($action->oldItem->getItemStack());
$new = $this->netItemStackToCore($action->newItem->getItemStack());
switch($action->sourceType){
case NetworkInventoryAction::SOURCE_CONTAINER:
if($action->windowId === ContainerIds::UI and $action->inventorySlot > 0){

View File

@ -32,7 +32,7 @@ use function strlen;
use function substr;
class EncryptionContext{
private const ENCRYPTION_SCHEME = "AES-256-CFB8";
private const ENCRYPTION_SCHEME = "AES-256-GCM";
private const CHECKSUM_ALGO = "sha256";
/** @var bool */
@ -52,17 +52,15 @@ class EncryptionContext{
private $encryptCounter = 0;
public function __construct(string $encryptionKey){
//TODO: ext/crypto doesn't offer us a way to disable padding. This doesn't matter at the moment because we're
//using CFB8, but this might change in the future. This is supposed to be CFB8 no-padding.
$this->key = $encryptionKey;
$iv = substr($this->key, 0, 16);
$this->decryptCipher = new Cipher(self::ENCRYPTION_SCHEME);
$this->decryptCipher->decryptInit($this->key, $iv);
$ivLength = $this->decryptCipher->getIVLength();
$this->decryptCipher->decryptInit($this->key, substr($this->key, 0, $ivLength));
$this->encryptCipher = new Cipher(self::ENCRYPTION_SCHEME);
$this->encryptCipher->encryptInit($this->key, $iv);
$ivLength = $this->encryptCipher->getIVLength();
$this->encryptCipher->encryptInit($this->key, substr($this->key, 0, $ivLength));
}
/**

View File

@ -253,7 +253,7 @@ class InGamePacketHandler extends PacketHandler{
)
) or (
$this->craftingTransaction !== null &&
!$networkInventoryAction->oldItem->equals($networkInventoryAction->newItem) &&
!$networkInventoryAction->oldItem->getItemStack()->equals($networkInventoryAction->newItem->getItemStack()) &&
$networkInventoryAction->sourceType === NetworkInventoryAction::SOURCE_CONTAINER &&
$networkInventoryAction->windowId === ContainerIds::UI &&
$networkInventoryAction->inventorySlot === UIInventorySlotOffset::CREATED_ITEM_OUTPUT

View File

@ -28,7 +28,7 @@ namespace pocketmine\network\mcpe\protocol;
use pocketmine\math\Vector3;
use pocketmine\network\mcpe\protocol\serializer\PacketSerializer;
use pocketmine\network\mcpe\protocol\types\entity\MetadataProperty;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStack;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStackWrapper;
class AddItemActorPacket extends DataPacket implements ClientboundPacket{
public const NETWORK_ID = ProtocolInfo::ADD_ITEM_ACTOR_PACKET;
@ -37,7 +37,7 @@ class AddItemActorPacket extends DataPacket implements ClientboundPacket{
public $entityUniqueId = null; //TODO
/** @var int */
public $entityRuntimeId;
/** @var ItemStack */
/** @var ItemStackWrapper */
public $item;
/** @var Vector3 */
public $position;
@ -54,7 +54,7 @@ class AddItemActorPacket extends DataPacket implements ClientboundPacket{
protected function decodePayload(PacketSerializer $in) : void{
$this->entityUniqueId = $in->getEntityUniqueId();
$this->entityRuntimeId = $in->getEntityRuntimeId();
$this->item = $in->getSlot();
$this->item = ItemStackWrapper::read($in);
$this->position = $in->getVector3();
$this->motion = $in->getVector3();
$this->metadata = $in->getEntityMetadata();
@ -64,7 +64,7 @@ class AddItemActorPacket extends DataPacket implements ClientboundPacket{
protected function encodePayload(PacketSerializer $out) : void{
$out->putEntityUniqueId($this->entityUniqueId ?? $this->entityRuntimeId);
$out->putEntityRuntimeId($this->entityRuntimeId);
$out->putSlot($this->item);
$this->item->write($out);
$out->putVector3($this->position);
$out->putVector3Nullable($this->motion);
$out->putEntityMetadata($this->metadata);

View File

@ -30,7 +30,7 @@ use pocketmine\network\mcpe\protocol\serializer\PacketSerializer;
use pocketmine\network\mcpe\protocol\types\DeviceOS;
use pocketmine\network\mcpe\protocol\types\entity\EntityLink;
use pocketmine\network\mcpe\protocol\types\entity\MetadataProperty;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStack;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStackWrapper;
use Ramsey\Uuid\UuidInterface;
use function count;
@ -57,7 +57,7 @@ class AddPlayerPacket extends DataPacket implements ClientboundPacket{
public $yaw = 0.0;
/** @var float|null */
public $headYaw = null; //TODO
/** @var ItemStack */
/** @var ItemStackWrapper */
public $item;
/**
* @var MetadataProperty[]
@ -99,7 +99,7 @@ class AddPlayerPacket extends DataPacket implements ClientboundPacket{
$this->pitch = $in->getLFloat();
$this->yaw = $in->getLFloat();
$this->headYaw = $in->getLFloat();
$this->item = $in->getSlot();
$this->item = ItemStackWrapper::read($in);
$this->metadata = $in->getEntityMetadata();
$this->uvarint1 = $in->getUnsignedVarInt();
@ -130,7 +130,7 @@ class AddPlayerPacket extends DataPacket implements ClientboundPacket{
$out->putLFloat($this->pitch);
$out->putLFloat($this->yaw);
$out->putLFloat($this->headYaw ?? $this->yaw);
$out->putSlot($this->item);
$this->item->write($out);
$out->putEntityMetadata($this->metadata);
$out->putUnsignedVarInt($this->uvarint1);

View File

@ -26,7 +26,7 @@ namespace pocketmine\network\mcpe\protocol;
#include <rules/DataPacket.h>
use pocketmine\network\mcpe\protocol\serializer\PacketSerializer;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStack;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStackWrapper;
use Ramsey\Uuid\UuidInterface;
use function count;
@ -39,9 +39,9 @@ class CraftingEventPacket extends DataPacket implements ServerboundPacket{
public $type;
/** @var UuidInterface */
public $id;
/** @var ItemStack[] */
/** @var ItemStackWrapper[] */
public $input = [];
/** @var ItemStack[] */
/** @var ItemStackWrapper[] */
public $output = [];
protected function decodePayload(PacketSerializer $in) : void{
@ -51,12 +51,12 @@ class CraftingEventPacket extends DataPacket implements ServerboundPacket{
$size = $in->getUnsignedVarInt();
for($i = 0; $i < $size and $i < 128; ++$i){
$this->input[] = $in->getSlot();
$this->input[] = ItemStackWrapper::read($in);
}
$size = $in->getUnsignedVarInt();
for($i = 0; $i < $size and $i < 128; ++$i){
$this->output[] = $in->getSlot();
$this->output[] = ItemStackWrapper::read($in);
}
}
@ -67,12 +67,12 @@ class CraftingEventPacket extends DataPacket implements ServerboundPacket{
$out->putUnsignedVarInt(count($this->input));
foreach($this->input as $item){
$out->putSlot($item);
$item->write($out);
}
$out->putUnsignedVarInt(count($this->output));
foreach($this->output as $item){
$out->putSlot($item);
$item->write($out);
}
}

View File

@ -51,8 +51,6 @@ class InventoryTransactionPacket extends DataPacket implements ClientboundPacket
public $requestId;
/** @var InventoryTransactionChangedSlotsHack[] */
public $requestChangedSlots;
/** @var bool */
public $hasItemStackIds;
/** @var TransactionData */
public $trData;
@ -67,8 +65,6 @@ class InventoryTransactionPacket extends DataPacket implements ClientboundPacket
$transactionType = $in->getUnsignedVarInt();
$this->hasItemStackIds = $in->getBool();
switch($transactionType){
case self::TYPE_NORMAL:
$this->trData = new NormalTransactionData();
@ -89,7 +85,7 @@ class InventoryTransactionPacket extends DataPacket implements ClientboundPacket
throw new PacketDecodeException("Unknown transaction type $transactionType");
}
$this->trData->decode($in, $this->hasItemStackIds);
$this->trData->decode($in);
}
protected function encodePayload(PacketSerializer $out) : void{
@ -103,9 +99,7 @@ class InventoryTransactionPacket extends DataPacket implements ClientboundPacket
$out->putUnsignedVarInt($this->trData->getTypeId());
$out->putBool($this->hasItemStackIds);
$this->trData->encode($out, $this->hasItemStackIds);
$this->trData->encode($out);
}
public function handle(PacketHandlerInterface $handler) : bool{

View File

@ -26,7 +26,7 @@ namespace pocketmine\network\mcpe\protocol;
#include <rules/DataPacket.h>
use pocketmine\network\mcpe\protocol\serializer\PacketSerializer;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStack;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStackWrapper;
class MobArmorEquipmentPacket extends DataPacket implements ClientboundPacket, ServerboundPacket{
public const NETWORK_ID = ProtocolInfo::MOB_ARMOR_EQUIPMENT_PACKET;
@ -36,16 +36,16 @@ class MobArmorEquipmentPacket extends DataPacket implements ClientboundPacket, S
//this intentionally doesn't use an array because we don't want any implicit dependencies on internal order
/** @var ItemStack */
/** @var ItemStackWrapper */
public $head;
/** @var ItemStack */
/** @var ItemStackWrapper */
public $chest;
/** @var ItemStack */
/** @var ItemStackWrapper */
public $legs;
/** @var ItemStack */
/** @var ItemStackWrapper */
public $feet;
public static function create(int $entityRuntimeId, ItemStack $head, ItemStack $chest, ItemStack $legs, ItemStack $feet) : self{
public static function create(int $entityRuntimeId, ItemStackWrapper $head, ItemStackWrapper $chest, ItemStackWrapper $legs, ItemStackWrapper $feet) : self{
$result = new self;
$result->entityRuntimeId = $entityRuntimeId;
$result->head = $head;
@ -58,18 +58,18 @@ class MobArmorEquipmentPacket extends DataPacket implements ClientboundPacket, S
protected function decodePayload(PacketSerializer $in) : void{
$this->entityRuntimeId = $in->getEntityRuntimeId();
$this->head = $in->getSlot();
$this->chest = $in->getSlot();
$this->legs = $in->getSlot();
$this->feet = $in->getSlot();
$this->head = ItemStackWrapper::read($in);
$this->chest = ItemStackWrapper::read($in);
$this->legs = ItemStackWrapper::read($in);
$this->feet = ItemStackWrapper::read($in);
}
protected function encodePayload(PacketSerializer $out) : void{
$out->putEntityRuntimeId($this->entityRuntimeId);
$out->putSlot($this->head);
$out->putSlot($this->chest);
$out->putSlot($this->legs);
$out->putSlot($this->feet);
$this->head->write($out);
$this->chest->write($out);
$this->legs->write($out);
$this->feet->write($out);
}
public function handle(PacketHandlerInterface $handler) : bool{

View File

@ -26,14 +26,14 @@ namespace pocketmine\network\mcpe\protocol;
#include <rules/DataPacket.h>
use pocketmine\network\mcpe\protocol\serializer\PacketSerializer;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStack;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStackWrapper;
class MobEquipmentPacket extends DataPacket implements ClientboundPacket, ServerboundPacket{
public const NETWORK_ID = ProtocolInfo::MOB_EQUIPMENT_PACKET;
/** @var int */
public $entityRuntimeId;
/** @var ItemStack */
/** @var ItemStackWrapper */
public $item;
/** @var int */
public $inventorySlot;
@ -42,7 +42,7 @@ class MobEquipmentPacket extends DataPacket implements ClientboundPacket, Server
/** @var int */
public $windowId = 0;
public static function create(int $entityRuntimeId, ItemStack $item, int $inventorySlot, int $windowId) : self{
public static function create(int $entityRuntimeId, ItemStackWrapper $item, int $inventorySlot, int $windowId) : self{
$result = new self;
$result->entityRuntimeId = $entityRuntimeId;
$result->item = $item;
@ -54,7 +54,7 @@ class MobEquipmentPacket extends DataPacket implements ClientboundPacket, Server
protected function decodePayload(PacketSerializer $in) : void{
$this->entityRuntimeId = $in->getEntityRuntimeId();
$this->item = $in->getSlot();
$this->item = ItemStackWrapper::read($in);
$this->inventorySlot = $in->getByte();
$this->hotbarSlot = $in->getByte();
$this->windowId = $in->getByte();
@ -62,7 +62,7 @@ class MobEquipmentPacket extends DataPacket implements ClientboundPacket, Server
protected function encodePayload(PacketSerializer $out) : void{
$out->putEntityRuntimeId($this->entityRuntimeId);
$out->putSlot($this->item);
$this->item->write($out);
$out->putByte($this->inventorySlot);
$out->putByte($this->hotbarSlot);
$out->putByte($this->windowId);

View File

@ -41,11 +41,11 @@ final class ProtocolInfo{
*/
/** Actual Minecraft: PE protocol version */
public const CURRENT_PROTOCOL = 428;
public const CURRENT_PROTOCOL = 431;
/** Current Minecraft PE version reported by the server. This is usually the earliest currently supported version. */
public const MINECRAFT_VERSION = 'v1.16.210';
public const MINECRAFT_VERSION = 'v1.16.220';
/** Version number sent to clients in ping responses. */
public const MINECRAFT_VERSION_NETWORK = '1.16.210';
public const MINECRAFT_VERSION_NETWORK = '1.16.220';
public const LOGIN_PACKET = 0x01;
public const PLAY_STATUS_PACKET = 0x02;

View File

@ -25,7 +25,9 @@ namespace pocketmine\network\mcpe\protocol\serializer;
#include <rules/DataPacket.h>
use pocketmine\entity\Entity;
use pocketmine\math\Vector3;
use pocketmine\nbt\LittleEndianNbtSerializer;
use pocketmine\nbt\NbtDataException;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\TreeRoot;
@ -216,49 +218,83 @@ class PacketSerializer extends BinaryStream{
* @throws PacketDecodeException
* @throws BinaryDataException
*/
public function getSlot() : ItemStack{
public function getItemStackWithoutStackId() : ItemStack{
return $this->getItemStack(function() : void{
//NOOP
});
}
public function putItemStackWithoutStackId(ItemStack $item) : void{
$this->putItemStack($item, function() : void{
//NOOP
});
}
/**
* @phpstan-param \Closure(PacketSerializer) : void $readExtraCrapInTheMiddle
*
* @throws PacketDecodeException
* @throws BinaryDataException
*/
public function getItemStack(\Closure $readExtraCrapInTheMiddle) : ItemStack{
$id = $this->getVarInt();
if($id === 0){
return ItemStack::null();
}
$auxValue = $this->getVarInt();
$meta = $auxValue >> 8;
$count = $auxValue & 0xff;
$count = $this->getLShort();
$meta = $this->getUnsignedVarInt();
$nbtLen = $this->getLShort();
$readExtraCrapInTheMiddle($this);
/** @var CompoundTag|null $compound */
$compound = null;
if($nbtLen === 0xffff){
$nbtDataVersion = $this->getByte();
if($nbtDataVersion !== 1){
throw new PacketDecodeException("Unexpected NBT data version $nbtDataVersion");
$blockRuntimeId = $this->getVarInt();
$extraData = new PacketSerializer($this->getString());
$shieldItemRuntimeId = $this->shieldItemRuntimeId;
return (static function() use ($extraData, $id, $meta, $count, $blockRuntimeId, $shieldItemRuntimeId) : ItemStack{
$nbtLen = $extraData->getLShort();
/** @var CompoundTag|null $compound */
$compound = null;
if($nbtLen === 0xffff){
$nbtDataVersion = $extraData->getByte();
if($nbtDataVersion !== 1){
throw new PacketDecodeException("Unexpected NBT data version $nbtDataVersion");
}
$offset = $extraData->getOffset();
try{
$compound = (new LittleEndianNbtSerializer())->read($extraData->getBuffer(), $offset, 512)->mustGetCompoundTag();
}catch(NbtDataException $e){
throw PacketDecodeException::wrap($e, "Failed decoding NBT root");
}finally{
$extraData->setOffset($offset);
}
}elseif($nbtLen !== 0){
throw new PacketDecodeException("Unexpected fake NBT length $nbtLen");
}
$compound = $this->getNbtCompoundRoot();
}elseif($nbtLen !== 0){
throw new PacketDecodeException("Unexpected fake NBT length $nbtLen");
}
$canPlaceOn = [];
for($i = 0, $canPlaceOnCount = $this->getVarInt(); $i < $canPlaceOnCount; ++$i){
$canPlaceOn[] = $this->getString();
}
$canPlaceOn = [];
for($i = 0, $canPlaceOnCount = $extraData->getLInt(); $i < $canPlaceOnCount; ++$i){
$canPlaceOn[] = $extraData->get($extraData->getLShort());
}
$canDestroy = [];
for($i = 0, $canDestroyCount = $this->getVarInt(); $i < $canDestroyCount; ++$i){
$canDestroy[] = $this->getString();
}
$canDestroy = [];
for($i = 0, $canDestroyCount = $extraData->getLInt(); $i < $canDestroyCount; ++$i){
$canDestroy[] = $extraData->get($extraData->getLShort());
}
$shieldBlockingTick = null;
if($id === $this->shieldItemRuntimeId){
$shieldBlockingTick = $this->getVarLong();
}
$shieldBlockingTick = null;
if($id === $shieldItemRuntimeId){
$shieldBlockingTick = $extraData->getLLong();
}
return new ItemStack($id, $meta, $count, $compound, $canPlaceOn, $canDestroy, $shieldBlockingTick);
return new ItemStack($id, $meta, $count, $blockRuntimeId, $compound, $canPlaceOn, $canDestroy, $shieldBlockingTick);
})();
}
public function putSlot(ItemStack $item) : void{
/**
* @phpstan-param \Closure(PacketSerializer) : void $writeExtraCrapInTheMiddle
*/
public function putItemStack(ItemStack $item, \Closure $writeExtraCrapInTheMiddle) : void{
if($item->getId() === 0){
$this->putVarInt(0);
@ -266,31 +302,42 @@ class PacketSerializer extends BinaryStream{
}
$this->putVarInt($item->getId());
$auxValue = (($item->getMeta() & 0x7fff) << 8) | $item->getCount();
$this->putVarInt($auxValue);
$this->putLShort($item->getCount());
$this->putUnsignedVarInt($item->getMeta());
$nbt = $item->getNbt();
if($nbt !== null){
$this->putLShort(0xffff);
$this->putByte(1); //TODO: NBT data version (?)
$this->put((new NetworkNbtSerializer())->write(new TreeRoot($nbt)));
}else{
$this->putLShort(0);
}
$writeExtraCrapInTheMiddle($this);
$this->putVarInt(count($item->getCanPlaceOn()));
foreach($item->getCanPlaceOn() as $entry){
$this->putString($entry);
}
$this->putVarInt(count($item->getCanDestroy()));
foreach($item->getCanDestroy() as $entry){
$this->putString($entry);
}
$this->putVarInt($item->getBlockRuntimeId());
$shieldItemRuntimeId = $this->shieldItemRuntimeId;
$this->putString((static function() use ($item, $shieldItemRuntimeId) : string{
$extraData = new PacketSerializer();
$blockingTick = $item->getShieldBlockingTick();
if($item->getId() === $this->shieldItemRuntimeId){
$this->putVarLong($blockingTick ?? 0);
}
$nbt = $item->getNbt();
if($nbt !== null){
$extraData->putLShort(0xffff);
$extraData->putByte(1); //TODO: NBT data version (?)
$extraData->put((new LittleEndianNbtSerializer())->write(new TreeRoot($nbt)));
}else{
$extraData->putLShort(0);
}
$extraData->putLInt(count($item->getCanPlaceOn()));
foreach($item->getCanPlaceOn() as $entry){
$extraData->putLShort(strlen($entry));
$extraData->put($entry);
}
$extraData->putLInt(count($item->getCanDestroy()));
foreach($item->getCanDestroy() as $entry){
$extraData->putLShort(strlen($entry));
$extraData->put($entry);
}
$blockingTick = $item->getShieldBlockingTick();
if($item->getId() === $shieldItemRuntimeId){
$extraData->putLLong($blockingTick ?? 0);
}
return $extraData->getBuffer();
})());
}
public function getRecipeIngredient() : RecipeIngredient{

View File

@ -43,12 +43,12 @@ final class CreativeContentEntry{
public static function read(PacketSerializer $in) : self{
$entryId = $in->readGenericTypeNetworkId();
$item = $in->getSlot();
$item = $in->getItemStackWithoutStackId();
return new self($entryId, $item);
}
public function write(PacketSerializer $out) : void{
$out->writeGenericTypeNetworkId($this->entryId);
$out->putSlot($this->item);
$out->putItemStackWithoutStackId($this->item);
}
}

View File

@ -37,6 +37,8 @@ final class ItemStack implements \JsonSerializable{
private $meta;
/** @var int */
private $count;
/** @var int */
private $blockRuntimeId;
/** @var string[] */
private $canPlaceOn;
/** @var string[] */
@ -50,10 +52,11 @@ final class ItemStack implements \JsonSerializable{
* @param string[] $canPlaceOn
* @param string[] $canDestroy
*/
public function __construct(int $id, int $meta, int $count, ?CompoundTag $nbt, array $canPlaceOn, array $canDestroy, ?int $shieldBlockingTick = null){
public function __construct(int $id, int $meta, int $count, int $blockRuntimeId, ?CompoundTag $nbt, array $canPlaceOn, array $canDestroy, ?int $shieldBlockingTick = null){
$this->id = $id;
$this->meta = $meta;
$this->count = $count;
$this->blockRuntimeId = $blockRuntimeId;
$this->canPlaceOn = $canPlaceOn;
$this->canDestroy = $canDestroy;
$this->nbt = $nbt;
@ -61,7 +64,7 @@ final class ItemStack implements \JsonSerializable{
}
public static function null() : self{
return new self(0, 0, 0, null, [], [], null);
return new self(0, 0, 0, 0, null, [], [], null);
}
public function getId() : int{
@ -76,6 +79,8 @@ final class ItemStack implements \JsonSerializable{
return $this->count;
}
public function getBlockRuntimeId() : int{ return $this->blockRuntimeId; }
/**
* @return string[]
*/
@ -103,6 +108,7 @@ final class ItemStack implements \JsonSerializable{
$this->id === $itemStack->id &&
$this->meta === $itemStack->meta &&
$this->count === $itemStack->count &&
$this->blockRuntimeId === $itemStack->blockRuntimeId &&
$this->canPlaceOn === $itemStack->canPlaceOn &&
$this->canDestroy === $itemStack->canDestroy &&
$this->shieldBlockingTick === $itemStack->shieldBlockingTick && (
@ -117,6 +123,7 @@ final class ItemStack implements \JsonSerializable{
"id" => $this->id,
"meta" => $this->meta,
"count" => $this->count,
"blockRuntimeId" => $this->blockRuntimeId,
];
if(count($this->canPlaceOn) > 0){
$result["canPlaceOn"] = $this->canPlaceOn;

View File

@ -46,13 +46,22 @@ final class ItemStackWrapper{
public function getItemStack() : ItemStack{ return $this->itemStack; }
public static function read(PacketSerializer $in) : self{
$stackId = $in->readGenericTypeNetworkId();
$stack = $in->getSlot();
$stackId = 0;
$stack = $in->getItemStack(function(PacketSerializer $in) use (&$stackId) : void{
$hasNetId = $in->getBool();
if($hasNetId){
$stackId = $in->readGenericTypeNetworkId();
}
});
return new self($stackId, $stack);
}
public function write(PacketSerializer $out) : void{
$out->writeGenericTypeNetworkId($this->stackId);
$out->putSlot($this->itemStack);
$out->putItemStack($this->itemStack, function(PacketSerializer $out) : void{
$out->putBool($this->stackId !== 0);
if($this->stackId !== 0){
$out->writeGenericTypeNetworkId($this->stackId);
}
});
}
}

View File

@ -72,12 +72,10 @@ class NetworkInventoryAction{
public $sourceFlags = 0;
/** @var int */
public $inventorySlot;
/** @var ItemStack */
/** @var ItemStackWrapper */
public $oldItem;
/** @var ItemStack */
/** @var ItemStackWrapper */
public $newItem;
/** @var int|null */
public $newItemStackId = null;
/**
* @return $this
@ -85,7 +83,7 @@ class NetworkInventoryAction{
* @throws BinaryDataException
* @throws PacketDecodeException
*/
public function read(PacketSerializer $packet, bool $hasItemStackIds) : NetworkInventoryAction{
public function read(PacketSerializer $packet) : NetworkInventoryAction{
$this->sourceType = $packet->getUnsignedVarInt();
switch($this->sourceType){
@ -105,11 +103,8 @@ class NetworkInventoryAction{
}
$this->inventorySlot = $packet->getUnsignedVarInt();
$this->oldItem = $packet->getSlot();
$this->newItem = $packet->getSlot();
if($hasItemStackIds){
$this->newItemStackId = $packet->readGenericTypeNetworkId();
}
$this->oldItem = ItemStackWrapper::read($packet);
$this->newItem = ItemStackWrapper::read($packet);
return $this;
}
@ -117,7 +112,7 @@ class NetworkInventoryAction{
/**
* @throws \InvalidArgumentException
*/
public function write(PacketSerializer $packet, bool $hasItemStackIds) : void{
public function write(PacketSerializer $packet) : void{
$packet->putUnsignedVarInt($this->sourceType);
switch($this->sourceType){
@ -137,13 +132,7 @@ class NetworkInventoryAction{
}
$packet->putUnsignedVarInt($this->inventorySlot);
$packet->putSlot($this->oldItem);
$packet->putSlot($this->newItem);
if($hasItemStackIds){
if($this->newItemStackId === null){
throw new \InvalidStateException("Item stack ID for newItem must be provided");
}
$packet->writeGenericTypeNetworkId($this->newItemStackId);
}
$this->oldItem->write($packet);
$this->newItem->write($packet);
}
}

View File

@ -35,7 +35,7 @@ class ReleaseItemTransactionData extends TransactionData{
private $actionType;
/** @var int */
private $hotbarSlot;
/** @var ItemStack */
/** @var ItemStackWrapper */
private $itemInHand;
/** @var Vector3 */
private $headPos;
@ -48,7 +48,7 @@ class ReleaseItemTransactionData extends TransactionData{
return $this->hotbarSlot;
}
public function getItemInHand() : ItemStack{
public function getItemInHand() : ItemStackWrapper{
return $this->itemInHand;
}
@ -63,21 +63,21 @@ class ReleaseItemTransactionData extends TransactionData{
protected function decodeData(PacketSerializer $stream) : void{
$this->actionType = $stream->getUnsignedVarInt();
$this->hotbarSlot = $stream->getVarInt();
$this->itemInHand = $stream->getSlot();
$this->itemInHand = ItemStackWrapper::read($stream);
$this->headPos = $stream->getVector3();
}
protected function encodeData(PacketSerializer $stream) : void{
$stream->putUnsignedVarInt($this->actionType);
$stream->putVarInt($this->hotbarSlot);
$stream->putSlot($this->itemInHand);
$this->itemInHand->write($stream);
$stream->putVector3($this->headPos);
}
/**
* @param NetworkInventoryAction[] $actions
*/
public static function new(array $actions, int $actionType, int $hotbarSlot, ItemStack $itemInHand, Vector3 $headPos) : self{
public static function new(array $actions, int $actionType, int $hotbarSlot, ItemStackWrapper $itemInHand, Vector3 $headPos) : self{
$result = new self;
$result->actions = $actions;
$result->actionType = $actionType;

View File

@ -45,10 +45,10 @@ abstract class TransactionData{
* @throws BinaryDataException
* @throws PacketDecodeException
*/
final public function decode(PacketSerializer $stream, bool $hasItemStackIds) : void{
final public function decode(PacketSerializer $stream) : void{
$actionCount = $stream->getUnsignedVarInt();
for($i = 0; $i < $actionCount; ++$i){
$this->actions[] = (new NetworkInventoryAction())->read($stream, $hasItemStackIds);
$this->actions[] = (new NetworkInventoryAction())->read($stream);
}
$this->decodeData($stream);
}
@ -59,10 +59,10 @@ abstract class TransactionData{
*/
abstract protected function decodeData(PacketSerializer $stream) : void;
final public function encode(PacketSerializer $stream, bool $hasItemStackIds) : void{
final public function encode(PacketSerializer $stream) : void{
$stream->putUnsignedVarInt(count($this->actions));
foreach($this->actions as $action){
$action->write($stream, $hasItemStackIds);
$action->write($stream);
}
$this->encodeData($stream);
}

View File

@ -37,7 +37,7 @@ class UseItemOnEntityTransactionData extends TransactionData{
private $actionType;
/** @var int */
private $hotbarSlot;
/** @var ItemStack */
/** @var ItemStackWrapper */
private $itemInHand;
/** @var Vector3 */
private $playerPos;
@ -56,7 +56,7 @@ class UseItemOnEntityTransactionData extends TransactionData{
return $this->hotbarSlot;
}
public function getItemInHand() : ItemStack{
public function getItemInHand() : ItemStackWrapper{
return $this->itemInHand;
}
@ -76,7 +76,7 @@ class UseItemOnEntityTransactionData extends TransactionData{
$this->entityRuntimeId = $stream->getEntityRuntimeId();
$this->actionType = $stream->getUnsignedVarInt();
$this->hotbarSlot = $stream->getVarInt();
$this->itemInHand = $stream->getSlot();
$this->itemInHand = ItemStackWrapper::read($stream);
$this->playerPos = $stream->getVector3();
$this->clickPos = $stream->getVector3();
}
@ -85,7 +85,7 @@ class UseItemOnEntityTransactionData extends TransactionData{
$stream->putEntityRuntimeId($this->entityRuntimeId);
$stream->putUnsignedVarInt($this->actionType);
$stream->putVarInt($this->hotbarSlot);
$stream->putSlot($this->itemInHand);
$this->itemInHand->write($stream);
$stream->putVector3($this->playerPos);
$stream->putVector3($this->clickPos);
}
@ -93,7 +93,7 @@ class UseItemOnEntityTransactionData extends TransactionData{
/**
* @param NetworkInventoryAction[] $actions
*/
public static function new(array $actions, int $entityRuntimeId, int $actionType, int $hotbarSlot, ItemStack $itemInHand, Vector3 $playerPos, Vector3 $clickPos) : self{
public static function new(array $actions, int $entityRuntimeId, int $actionType, int $hotbarSlot, ItemStackWrapper $itemInHand, Vector3 $playerPos, Vector3 $clickPos) : self{
$result = new self;
$result->actions = $actions;
$result->entityRuntimeId = $entityRuntimeId;

View File

@ -40,7 +40,7 @@ class UseItemTransactionData extends TransactionData{
private $face;
/** @var int */
private $hotbarSlot;
/** @var ItemStack */
/** @var ItemStackWrapper */
private $itemInHand;
/** @var Vector3 */
private $playerPos;
@ -65,7 +65,7 @@ class UseItemTransactionData extends TransactionData{
return $this->hotbarSlot;
}
public function getItemInHand() : ItemStack{
public function getItemInHand() : ItemStackWrapper{
return $this->itemInHand;
}
@ -92,7 +92,7 @@ class UseItemTransactionData extends TransactionData{
$this->blockPos = new Vector3($x, $y, $z);
$this->face = $stream->getVarInt();
$this->hotbarSlot = $stream->getVarInt();
$this->itemInHand = $stream->getSlot();
$this->itemInHand = ItemStackWrapper::read($stream);
$this->playerPos = $stream->getVector3();
$this->clickPos = $stream->getVector3();
$this->blockRuntimeId = $stream->getUnsignedVarInt();
@ -103,7 +103,7 @@ class UseItemTransactionData extends TransactionData{
$stream->putBlockPosition($this->blockPos->x, $this->blockPos->y, $this->blockPos->z);
$stream->putVarInt($this->face);
$stream->putVarInt($this->hotbarSlot);
$stream->putSlot($this->itemInHand);
$this->itemInHand->write($stream);
$stream->putVector3($this->playerPos);
$stream->putVector3($this->clickPos);
$stream->putUnsignedVarInt($this->blockRuntimeId);
@ -112,7 +112,7 @@ class UseItemTransactionData extends TransactionData{
/**
* @param NetworkInventoryAction[] $actions
*/
public static function new(array $actions, int $actionType, Vector3 $blockPos, int $face, int $hotbarSlot, ItemStack $itemInHand, Vector3 $playerPos, Vector3 $clickPos, int $blockRuntimeId) : self{
public static function new(array $actions, int $actionType, Vector3 $blockPos, int $face, int $hotbarSlot, ItemStackWrapper $itemInHand, Vector3 $playerPos, Vector3 $clickPos, int $blockRuntimeId) : self{
$result = new self;
$result->actions = $actions;
$result->actionType = $actionType;

View File

@ -58,7 +58,7 @@ final class DeprecatedCraftingResultsStackRequestAction extends ItemStackRequest
public static function read(PacketSerializer $in) : self{
$results = [];
for($i = 0, $len = $in->getUnsignedVarInt(); $i < $len; ++$i){
$results[] = $in->getSlot();
$results[] = $in->getItemStackWithoutStackId();
}
$iterations = $in->getByte();
return new self($results, $iterations);
@ -67,7 +67,7 @@ final class DeprecatedCraftingResultsStackRequestAction extends ItemStackRequest
public function write(PacketSerializer $out) : void{
$out->putUnsignedVarInt(count($this->results));
foreach($this->results as $result){
$out->putSlot($result);
$out->putItemStackWithoutStackId($result);
}
$out->putByte($this->iterations);
}

View File

@ -68,7 +68,7 @@ final class FurnaceRecipe extends RecipeWithTypeId{
if($typeId === CraftingDataPacket::ENTRY_FURNACE_DATA){
$inputData = $in->getVarInt();
}
$output = $in->getSlot();
$output = $in->getItemStackWithoutStackId();
$block = $in->getString();
return new self($typeId, $inputId, $inputData, $output, $block);
@ -79,7 +79,7 @@ final class FurnaceRecipe extends RecipeWithTypeId{
if($this->getTypeId() === CraftingDataPacket::ENTRY_FURNACE_DATA){
$out->putVarInt($this->inputMeta);
}
$out->putSlot($this->result);
$out->putItemStackWithoutStackId($this->result);
$out->putString($this->blockName);
}
}

View File

@ -127,7 +127,7 @@ final class ShapedRecipe extends RecipeWithTypeId{
$output = [];
for($k = 0, $resultCount = $in->getUnsignedVarInt(); $k < $resultCount; ++$k){
$output[] = $in->getSlot();
$output[] = $in->getItemStackWithoutStackId();
}
$uuid = $in->getUUID();
$block = $in->getString();
@ -149,7 +149,7 @@ final class ShapedRecipe extends RecipeWithTypeId{
$out->putUnsignedVarInt(count($this->output));
foreach($this->output as $item){
$out->putSlot($item);
$out->putItemStackWithoutStackId($item);
}
$out->putUUID($this->uuid);

View File

@ -102,7 +102,7 @@ final class ShapelessRecipe extends RecipeWithTypeId{
}
$output = [];
for($k = 0, $resultCount = $in->getUnsignedVarInt(); $k < $resultCount; ++$k){
$output[] = $in->getSlot();
$output[] = $in->getItemStackWithoutStackId();
}
$uuid = $in->getUUID();
$block = $in->getString();
@ -121,7 +121,7 @@ final class ShapelessRecipe extends RecipeWithTypeId{
$out->putUnsignedVarInt(count($this->outputs));
foreach($this->outputs as $item){
$out->putSlot($item);
$out->putItemStackWithoutStackId($item);
}
$out->putUUID($this->uuid);

View File

@ -35,6 +35,7 @@ use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataProperties;
use pocketmine\network\mcpe\protocol\types\entity\FloatMetadataProperty;
use pocketmine\network\mcpe\protocol\types\entity\LongMetadataProperty;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStack;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStackWrapper;
use pocketmine\network\mcpe\protocol\types\PlayerListEntry;
use Ramsey\Uuid\Uuid;
use function str_repeat;
@ -100,7 +101,7 @@ class FloatingTextParticle implements Particle{
$pk->username = $name;
$pk->entityRuntimeId = $this->entityId;
$pk->position = $pos; //TODO: check offset
$pk->item = ItemStack::null();
$pk->item = ItemStackWrapper::legacy(ItemStack::null());
$flags = (
1 << EntityMetadataFlags::IMMOBILE