From 98cdc80d379a6fd2ec51816ea189b81917192b6a Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 21 Nov 2020 01:07:25 +0000 Subject: [PATCH 1/6] Protocol changes for 1.16.100 --- src/pocketmine/Player.php | 24 ++- .../inventory/ContainerInventory.php | 1 + .../transaction/CraftingTransaction.php | 1 + .../network/mcpe/NetworkBinaryStream.php | 65 +++++-- .../network/mcpe/NetworkSession.php | 40 +++- .../mcpe/PlayerNetworkSessionAdapter.php | 5 - .../network/mcpe/convert/ItemTranslator.php | 183 ++++++++++++++++++ .../mcpe/convert/ItemTypeDictionary.php | 106 ++++++++++ .../mcpe/convert/RuntimeBlockMapping.php | 45 ++--- .../mcpe/protocol/AnimateEntityPacket.php | 108 +++++++++++ .../mcpe/protocol/CameraShakePacket.php | 72 +++++++ .../mcpe/protocol/ContainerClosePacket.php | 4 + .../CorrectPlayerMovePredictionPacket.php | 77 ++++++++ .../mcpe/protocol/CraftingDataPacket.php | 54 +++--- .../mcpe/protocol/ItemComponentPacket.php | 78 ++++++++ .../protocol/MotionPredictionHintsPacket.php | 70 +++++++ .../mcpe/protocol/MoveActorDeltaPacket.php | 32 +-- .../mcpe/protocol/MovePlayerPacket.php | 4 + .../network/mcpe/protocol/PacketPool.php | 8 +- .../mcpe/protocol/PlayerActionPacket.php | 2 +- .../mcpe/protocol/PlayerAuthInputPacket.php | 16 +- ...opertiesPacket.php => PlayerFogPacket.php} | 40 ++-- .../network/mcpe/protocol/ProtocolInfo.php | 16 +- .../mcpe/protocol/ResourcePackStackPacket.php | 10 +- .../mcpe/protocol/SetActorDataPacket.php | 5 + .../network/mcpe/protocol/StartGamePacket.php | 96 ++++----- .../mcpe/protocol/UpdateAttributesPacket.php | 4 + .../mcpe/protocol/types/BlockPaletteEntry.php | 43 ++++ .../mcpe/protocol/types/Experiments.php | 72 +++++++ .../types/ItemComponentPacketEntry.php | 43 ++++ .../ItemTypeEntry.php} | 37 ++-- .../protocol/types/PlayerMovementType.php | 31 +++ .../mcpe/protocol/types/SkinAnimation.php | 10 +- .../stackresponse/ItemStackResponse.php | 21 +- src/pocketmine/resources/vanilla | 2 +- 35 files changed, 1206 insertions(+), 219 deletions(-) create mode 100644 src/pocketmine/network/mcpe/convert/ItemTranslator.php create mode 100644 src/pocketmine/network/mcpe/convert/ItemTypeDictionary.php create mode 100644 src/pocketmine/network/mcpe/protocol/AnimateEntityPacket.php create mode 100644 src/pocketmine/network/mcpe/protocol/CameraShakePacket.php create mode 100644 src/pocketmine/network/mcpe/protocol/CorrectPlayerMovePredictionPacket.php create mode 100644 src/pocketmine/network/mcpe/protocol/ItemComponentPacket.php create mode 100644 src/pocketmine/network/mcpe/protocol/MotionPredictionHintsPacket.php rename src/pocketmine/network/mcpe/protocol/{UpdateBlockPropertiesPacket.php => PlayerFogPacket.php} (55%) create mode 100644 src/pocketmine/network/mcpe/protocol/types/BlockPaletteEntry.php create mode 100644 src/pocketmine/network/mcpe/protocol/types/Experiments.php create mode 100644 src/pocketmine/network/mcpe/protocol/types/ItemComponentPacketEntry.php rename src/pocketmine/network/mcpe/protocol/{ActorFallPacket.php => types/ItemTypeEntry.php} (50%) create mode 100644 src/pocketmine/network/mcpe/protocol/types/PlayerMovementType.php diff --git a/src/pocketmine/Player.php b/src/pocketmine/Player.php index 28fb46d57..066041b70 100644 --- a/src/pocketmine/Player.php +++ b/src/pocketmine/Player.php @@ -100,6 +100,7 @@ use pocketmine\nbt\tag\ByteTag; use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\DoubleTag; use pocketmine\nbt\tag\ListTag; +use pocketmine\network\mcpe\convert\ItemTypeDictionary; use pocketmine\network\mcpe\PlayerNetworkSessionAdapter; use pocketmine\network\mcpe\protocol\ActorEventPacket; use pocketmine\network\mcpe\protocol\AdventureSettingsPacket; @@ -148,6 +149,7 @@ use pocketmine\network\mcpe\protocol\types\CommandEnum; use pocketmine\network\mcpe\protocol\types\CommandParameter; use pocketmine\network\mcpe\protocol\types\ContainerIds; use pocketmine\network\mcpe\protocol\types\DimensionIds; +use pocketmine\network\mcpe\protocol\types\Experiments; use pocketmine\network\mcpe\protocol\types\GameMode; use pocketmine\network\mcpe\protocol\types\inventory\UIInventorySlotOffset; use pocketmine\network\mcpe\protocol\types\NetworkInventoryAction; @@ -1939,7 +1941,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{ $animation["ImageWidth"], base64_decode($animation["Image"], true)), $animation["Type"], - $animation["Frames"] + $animation["Frames"], + $animation["AnimationExpression"] ); } @@ -2179,6 +2182,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{ //but it does have an annoying side-effect when true: it makes //the client remove its own non-server-supplied resource packs. $pk->mustAccept = false; + $pk->experiments = new Experiments([], false); $this->dataPacket($pk); break; case ResourcePackClientResponsePacket::STATUS_COMPLETED: @@ -2244,6 +2248,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{ $pk->commandsEnabled = true; $pk->levelId = ""; $pk->worldName = $this->server->getMotd(); + $pk->experiments = new Experiments([], false); + $pk->itemTable = ItemTypeDictionary::getInstance()->getEntries(); $this->dataPacket($pk); $this->sendDataPacket(new AvailableActorIdentifiersPacket()); @@ -2870,7 +2876,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{ } public function handlePlayerAction(PlayerActionPacket $packet) : bool{ - if(!$this->spawned or (!$this->isAlive() and $packet->action !== PlayerActionPacket::ACTION_RESPAWN and $packet->action !== PlayerActionPacket::ACTION_DIMENSION_CHANGE_REQUEST)){ + if(!$this->spawned or (!$this->isAlive() and $packet->action !== PlayerActionPacket::ACTION_RESPAWN)){ return true; } @@ -2957,7 +2963,10 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{ case PlayerActionPacket::ACTION_STOP_SWIMMING: //TODO: handle this when it doesn't spam every damn tick (yet another spam bug!!) break; - case PlayerActionPacket::ACTION_INTERACT_BLOCK: //ignored (for now) + case PlayerActionPacket::ACTION_INTERACT_BLOCK: //TODO: ignored (for now) + break; + case PlayerActionPacket::ACTION_CREATIVE_PLAYER_DESTROY_BLOCK: + //TODO: do we need to handle this? break; default: $this->server->getLogger()->debug("Unhandled/unknown player action type " . $packet->action . " from " . $this->getName()); @@ -3039,6 +3048,12 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{ return true; } + /** @var int|null */ + private $closingWindowId = null; + + /** @internal */ + public function getClosingWindowId() : ?int{ return $this->closingWindowId; } + public function handleContainerClose(ContainerClosePacket $packet) : bool{ if(!$this->spawned){ return true; @@ -3050,12 +3065,15 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{ unset($this->openHardcodedWindows[$packet->windowId]); $pk = new ContainerClosePacket(); $pk->windowId = $packet->windowId; + $pk->server = false; $this->sendDataPacket($pk); return true; } if(isset($this->windowIndex[$packet->windowId])){ + $this->closingWindowId = $packet->windowId; (new InventoryCloseEvent($this->windowIndex[$packet->windowId], $this))->call(); $this->removeWindow($this->windowIndex[$packet->windowId]); + $this->closingWindowId = null; //removeWindow handles sending the appropriate return true; } diff --git a/src/pocketmine/inventory/ContainerInventory.php b/src/pocketmine/inventory/ContainerInventory.php index 747c4c379..a6231fc01 100644 --- a/src/pocketmine/inventory/ContainerInventory.php +++ b/src/pocketmine/inventory/ContainerInventory.php @@ -64,6 +64,7 @@ abstract class ContainerInventory extends BaseInventory{ public function onClose(Player $who) : void{ $pk = new ContainerClosePacket(); $pk->windowId = $who->getWindowId($this); + $pk->server = $who->getClosingWindowId() !== $pk->windowId; $who->dataPacket($pk); parent::onClose($who); } diff --git a/src/pocketmine/inventory/transaction/CraftingTransaction.php b/src/pocketmine/inventory/transaction/CraftingTransaction.php index 1599e19c7..01853fd3b 100644 --- a/src/pocketmine/inventory/transaction/CraftingTransaction.php +++ b/src/pocketmine/inventory/transaction/CraftingTransaction.php @@ -163,6 +163,7 @@ class CraftingTransaction extends InventoryTransaction{ */ $pk = new ContainerClosePacket(); $pk->windowId = Player::HARDCODED_CRAFTING_GRID_WINDOW_ID; + $pk->server = true; $this->source->dataPacket($pk); } diff --git a/src/pocketmine/network/mcpe/NetworkBinaryStream.php b/src/pocketmine/network/mcpe/NetworkBinaryStream.php index 1e8324212..fdf42b321 100644 --- a/src/pocketmine/network/mcpe/NetworkBinaryStream.php +++ b/src/pocketmine/network/mcpe/NetworkBinaryStream.php @@ -35,6 +35,9 @@ use pocketmine\math\Vector3; use pocketmine\nbt\NetworkLittleEndianNBTStream; use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\IntTag; +use pocketmine\nbt\tag\NamedTag; +use pocketmine\network\mcpe\convert\ItemTranslator; +use pocketmine\network\mcpe\convert\ItemTypeDictionary; use pocketmine\network\mcpe\protocol\types\CommandOriginData; use pocketmine\network\mcpe\protocol\types\EntityLink; use pocketmine\network\mcpe\protocol\types\GameRuleType; @@ -91,7 +94,8 @@ class NetworkBinaryStream extends BinaryStream{ $skinImage = $this->getSkinImage(); $animationType = $this->getLInt(); $animationFrames = $this->getLFloat(); - $animations[] = new SkinAnimation($skinImage, $animationType, $animationFrames); + $expressionType = $this->getLInt(); + $animations[] = new SkinAnimation($skinImage, $animationType, $animationFrames, $expressionType); } $capeData = $this->getSkinImage(); $geometryData = $this->getString(); @@ -186,15 +190,17 @@ class NetworkBinaryStream extends BinaryStream{ } public function getSlot() : Item{ - $id = $this->getVarInt(); - if($id === 0){ + $netId = $this->getVarInt(); + if($netId === 0){ return ItemFactory::get(0, 0, 0); } $auxValue = $this->getVarInt(); - $data = $auxValue >> 8; + $netData = $auxValue >> 8; $cnt = $auxValue & 0xff; + [$id, $meta] = ItemTranslator::getInstance()->fromNetworkId($netId, $netData); + $nbtLen = $this->getLShort(); /** @var CompoundTag|null $nbt */ @@ -223,12 +229,12 @@ class NetworkBinaryStream extends BinaryStream{ $this->getString(); } - if($id === ItemIds::SHIELD){ + if($netId === ItemTypeDictionary::getInstance()->fromStringId("minecraft:shield")){ $this->getVarLong(); //"blocking tick" (ffs mojang) } if($nbt !== null){ if($nbt->hasTag(self::DAMAGE_TAG, IntTag::class)){ - $data = $nbt->getInt(self::DAMAGE_TAG); + $meta = $nbt->getInt(self::DAMAGE_TAG); $nbt->removeTag(self::DAMAGE_TAG); if($nbt->count() === 0){ $nbt = null; @@ -242,7 +248,7 @@ class NetworkBinaryStream extends BinaryStream{ } } end: - return ItemFactory::get($id, $data, $cnt, $nbt); + return ItemFactory::get($id, $meta, $cnt, $nbt); } public function putSlot(Item $item) : void{ @@ -252,8 +258,10 @@ class NetworkBinaryStream extends BinaryStream{ return; } - $this->putVarInt($item->getId()); - $auxValue = (($item->getDamage() & 0x7fff) << 8) | $item->getCount(); + [$netId, $netData] = ItemTranslator::getInstance()->toNetworkId($item->getId(), $item->getDamage()); + + $this->putVarInt($netId); + $auxValue = (($netData & 0x7fff) << 8) | $item->getCount(); $this->putVarInt($auxValue); $nbt = null; @@ -284,20 +292,18 @@ class NetworkBinaryStream extends BinaryStream{ $this->putVarInt(0); //CanPlaceOn entry count (TODO) $this->putVarInt(0); //CanDestroy entry count (TODO) - if($item->getId() === ItemIds::SHIELD){ + if($netId === ItemTypeDictionary::getInstance()->fromStringId("minecraft:shield")){ $this->putVarLong(0); //"blocking tick" (ffs mojang) } } public function getRecipeIngredient() : Item{ - $id = $this->getVarInt(); - if($id === 0){ + $netId = $this->getVarInt(); + if($netId === 0){ return ItemFactory::get(ItemIds::AIR, 0, 0); } - $meta = $this->getVarInt(); - if($meta === 0x7fff){ - $meta = -1; - } + $netData = $this->getVarInt(); + [$id, $meta] = ItemTranslator::getInstance()->fromNetworkIdWithWildcardHandling($netId, $netData); $count = $this->getVarInt(); return ItemFactory::get($id, $meta, $count); } @@ -306,8 +312,14 @@ class NetworkBinaryStream extends BinaryStream{ if($item->isNull()){ $this->putVarInt(0); }else{ - $this->putVarInt($item->getId()); - $this->putVarInt($item->getDamage() & 0x7fff); + if($item->hasAnyDamageValue()){ + [$netId, ] = ItemTranslator::getInstance()->toNetworkId($item->getId(), 0); + $netData = 0x7fff; + }else{ + [$netId, $netData] = ItemTranslator::getInstance()->toNetworkId($item->getId(), $item->getDamage()); + } + $this->putVarInt($netId); + $this->putVarInt($netData); $this->putVarInt($item->getCount()); } } @@ -750,6 +762,23 @@ class NetworkBinaryStream extends BinaryStream{ $this->putVarInt($structureEditorData->structureRedstoneSaveMove); } + public function getNbtRoot() : NamedTag{ + $offset = $this->getOffset(); + try{ + return (new NetworkLittleEndianNBTStream())->read($this->getBuffer(), false, $offset, 512); + }finally{ + $this->setOffset($offset); + } + } + + public function getNbtCompoundRoot() : CompoundTag{ + $root = $this->getNbtRoot(); + if(!($root instanceof CompoundTag)){ + throw new \UnexpectedValueException("Expected TAG_Compound root"); + } + return $root; + } + public function readGenericTypeNetworkId() : int{ return $this->getVarInt(); } diff --git a/src/pocketmine/network/mcpe/NetworkSession.php b/src/pocketmine/network/mcpe/NetworkSession.php index 0ff494411..dfb39796f 100644 --- a/src/pocketmine/network/mcpe/NetworkSession.php +++ b/src/pocketmine/network/mcpe/NetworkSession.php @@ -24,7 +24,6 @@ declare(strict_types=1); namespace pocketmine\network\mcpe; use pocketmine\network\mcpe\protocol\ActorEventPacket; -use pocketmine\network\mcpe\protocol\ActorFallPacket; use pocketmine\network\mcpe\protocol\ActorPickRequestPacket; use pocketmine\network\mcpe\protocol\AddActorPacket; use pocketmine\network\mcpe\protocol\AddBehaviorTreePacket; @@ -33,6 +32,7 @@ use pocketmine\network\mcpe\protocol\AddItemActorPacket; use pocketmine\network\mcpe\protocol\AddPaintingPacket; use pocketmine\network\mcpe\protocol\AddPlayerPacket; use pocketmine\network\mcpe\protocol\AdventureSettingsPacket; +use pocketmine\network\mcpe\protocol\AnimateEntityPacket; use pocketmine\network\mcpe\protocol\AnimatePacket; use pocketmine\network\mcpe\protocol\AnvilDamagePacket; use pocketmine\network\mcpe\protocol\AutomationClientConnectPacket; @@ -45,6 +45,7 @@ use pocketmine\network\mcpe\protocol\BlockPickRequestPacket; use pocketmine\network\mcpe\protocol\BookEditPacket; use pocketmine\network\mcpe\protocol\BossEventPacket; use pocketmine\network\mcpe\protocol\CameraPacket; +use pocketmine\network\mcpe\protocol\CameraShakePacket; use pocketmine\network\mcpe\protocol\ChangeDimensionPacket; use pocketmine\network\mcpe\protocol\ChunkRadiusUpdatedPacket; use pocketmine\network\mcpe\protocol\ClientboundMapItemDataPacket; @@ -60,6 +61,7 @@ use pocketmine\network\mcpe\protocol\CompletedUsingItemPacket; use pocketmine\network\mcpe\protocol\ContainerClosePacket; use pocketmine\network\mcpe\protocol\ContainerOpenPacket; use pocketmine\network\mcpe\protocol\ContainerSetDataPacket; +use pocketmine\network\mcpe\protocol\CorrectPlayerMovePredictionPacket; use pocketmine\network\mcpe\protocol\CraftingDataPacket; use pocketmine\network\mcpe\protocol\CraftingEventPacket; use pocketmine\network\mcpe\protocol\CreativeContentPacket; @@ -77,6 +79,7 @@ use pocketmine\network\mcpe\protocol\InteractPacket; use pocketmine\network\mcpe\protocol\InventoryContentPacket; use pocketmine\network\mcpe\protocol\InventorySlotPacket; use pocketmine\network\mcpe\protocol\InventoryTransactionPacket; +use pocketmine\network\mcpe\protocol\ItemComponentPacket; use pocketmine\network\mcpe\protocol\ItemFrameDropItemPacket; use pocketmine\network\mcpe\protocol\ItemStackRequestPacket; use pocketmine\network\mcpe\protocol\ItemStackResponsePacket; @@ -96,6 +99,7 @@ use pocketmine\network\mcpe\protocol\MobEffectPacket; use pocketmine\network\mcpe\protocol\MobEquipmentPacket; use pocketmine\network\mcpe\protocol\ModalFormRequestPacket; use pocketmine\network\mcpe\protocol\ModalFormResponsePacket; +use pocketmine\network\mcpe\protocol\MotionPredictionHintsPacket; use pocketmine\network\mcpe\protocol\MoveActorAbsolutePacket; use pocketmine\network\mcpe\protocol\MoveActorDeltaPacket; use pocketmine\network\mcpe\protocol\MovePlayerPacket; @@ -111,6 +115,7 @@ use pocketmine\network\mcpe\protocol\PlayerActionPacket; use pocketmine\network\mcpe\protocol\PlayerArmorDamagePacket; use pocketmine\network\mcpe\protocol\PlayerAuthInputPacket; use pocketmine\network\mcpe\protocol\PlayerEnchantOptionsPacket; +use pocketmine\network\mcpe\protocol\PlayerFogPacket; use pocketmine\network\mcpe\protocol\PlayerHotbarPacket; use pocketmine\network\mcpe\protocol\PlayerInputPacket; use pocketmine\network\mcpe\protocol\PlayerListPacket; @@ -171,7 +176,6 @@ use pocketmine\network\mcpe\protocol\TickSyncPacket; use pocketmine\network\mcpe\protocol\TransferPacket; use pocketmine\network\mcpe\protocol\UpdateAttributesPacket; use pocketmine\network\mcpe\protocol\UpdateBlockPacket; -use pocketmine\network\mcpe\protocol\UpdateBlockPropertiesPacket; use pocketmine\network\mcpe\protocol\UpdateBlockSyncedPacket; use pocketmine\network\mcpe\protocol\UpdateEquipPacket; use pocketmine\network\mcpe\protocol\UpdatePlayerGameTypePacket; @@ -325,10 +329,6 @@ abstract class NetworkSession{ return false; } - public function handleActorFall(ActorFallPacket $packet) : bool{ - return false; - } - public function handleHurtArmor(HurtArmorPacket $packet) : bool{ return false; } @@ -705,10 +705,6 @@ abstract class NetworkSession{ return false; } - public function handleUpdateBlockProperties(UpdateBlockPropertiesPacket $packet) : bool{ - return false; - } - public function handleClientCacheBlobStatus(ClientCacheBlobStatusPacket $packet) : bool{ return false; } @@ -796,4 +792,28 @@ abstract class NetworkSession{ public function handlePacketViolationWarning(PacketViolationWarningPacket $packet) : bool{ return false; } + + public function handleMotionPredictionHints(MotionPredictionHintsPacket $packet) : bool{ + return false; + } + + public function handleAnimateEntity(AnimateEntityPacket $packet) : bool{ + return false; + } + + public function handleCameraShake(CameraShakePacket $packet) : bool{ + return false; + } + + public function handlePlayerFog(PlayerFogPacket $packet) : bool{ + return false; + } + + public function handleCorrectPlayerMovePrediction(CorrectPlayerMovePredictionPacket $packet) : bool{ + return false; + } + + public function handleItemComponent(ItemComponentPacket $packet) : bool{ + return false; + } } diff --git a/src/pocketmine/network/mcpe/PlayerNetworkSessionAdapter.php b/src/pocketmine/network/mcpe/PlayerNetworkSessionAdapter.php index 59203e2f8..b300d3142 100644 --- a/src/pocketmine/network/mcpe/PlayerNetworkSessionAdapter.php +++ b/src/pocketmine/network/mcpe/PlayerNetworkSessionAdapter.php @@ -25,7 +25,6 @@ namespace pocketmine\network\mcpe; use pocketmine\event\server\DataPacketReceiveEvent; use pocketmine\network\mcpe\protocol\ActorEventPacket; -use pocketmine\network\mcpe\protocol\ActorFallPacket; use pocketmine\network\mcpe\protocol\ActorPickRequestPacket; use pocketmine\network\mcpe\protocol\AdventureSettingsPacket; use pocketmine\network\mcpe\protocol\AnimatePacket; @@ -174,10 +173,6 @@ class PlayerNetworkSessionAdapter extends NetworkSession{ return $this->player->handlePlayerAction($packet); } - public function handleActorFall(ActorFallPacket $packet) : bool{ - return true; //Not used - } - public function handleAnimate(AnimatePacket $packet) : bool{ return $this->player->handleAnimate($packet); } diff --git a/src/pocketmine/network/mcpe/convert/ItemTranslator.php b/src/pocketmine/network/mcpe/convert/ItemTranslator.php new file mode 100644 index 000000000..30110ed13 --- /dev/null +++ b/src/pocketmine/network/mcpe/convert/ItemTranslator.php @@ -0,0 +1,183 @@ + + */ + private $simpleCoreToNetMapping = []; + /** + * @var int[] + * @phpstan-var array + */ + private $simpleNetToCoreMapping = []; + + /** + * runtimeId = array[internalId][metadata] + * @var int[][] + * @phpstan-var array> + */ + private $complexCoreToNetMapping = []; + /** + * [internalId, metadata] = array[runtimeId] + * @var int[][] + * @phpstan-var array + */ + private $complexNetToCoreMapping = []; + + private static function make() : self{ + $data = file_get_contents(\pocketmine\RESOURCE_PATH . '/vanilla/r16_to_current_item_map.json'); + if($data === false) throw new AssumptionFailedError("Missing required resource file"); + $json = json_decode($data, true); + if(!is_array($json) or !isset($json["simple"], $json["complex"]) || !is_array($json["simple"]) || !is_array($json["complex"])){ + throw new AssumptionFailedError("Invalid item table format"); + } + + $legacyStringToIntMapRaw = file_get_contents(\pocketmine\RESOURCE_PATH . '/vanilla/item_id_map.json'); + if($legacyStringToIntMapRaw === false){ + throw new AssumptionFailedError("Missing required resource file"); + } + $legacyStringToIntMap = json_decode($legacyStringToIntMapRaw, true); + if(!is_array($legacyStringToIntMap)){ + throw new AssumptionFailedError("Invalid mapping table format"); + } + + /** @phpstan-var array $simpleMappings */ + $simpleMappings = []; + foreach($json["simple"] as $oldId => $newId){ + if(!is_string($oldId) || !is_string($newId)){ + throw new AssumptionFailedError("Invalid item table format"); + } + $simpleMappings[$newId] = $legacyStringToIntMap[$oldId]; + } + foreach($legacyStringToIntMap as $stringId => $intId){ + if(isset($simpleMappings[$stringId])){ + throw new \UnexpectedValueException("Old ID $stringId collides with new ID"); + } + $simpleMappings[$stringId] = $intId; + } + + /** @phpstan-var array $complexMappings */ + $complexMappings = []; + foreach($json["complex"] as $oldId => $map){ + if(!is_string($oldId) || !is_array($map)){ + throw new AssumptionFailedError("Invalid item table format"); + } + foreach($map as $meta => $newId){ + if(!is_numeric($meta) || !is_string($newId)){ + throw new AssumptionFailedError("Invalid item table format"); + } + $complexMappings[$newId] = [$legacyStringToIntMap[$oldId], (int) $meta]; + } + } + + return new self(ItemTypeDictionary::getInstance(), $simpleMappings, $complexMappings); + } + + /** + * @param int[] $simpleMappings + * @param int[][] $complexMappings + * @phpstan-param array $simpleMappings + * @phpstan-param array> $complexMappings + */ + public function __construct(ItemTypeDictionary $dictionary, array $simpleMappings, array $complexMappings){ + foreach($dictionary->getEntries() as $entry){ + $stringId = $entry->getStringId(); + $netId = $entry->getNumericId(); + if(isset($complexMappings[$stringId])){ + [$id, $meta] = $complexMappings[$stringId]; + $this->complexCoreToNetMapping[$id][$meta] = $netId; + $this->complexNetToCoreMapping[$netId] = [$id, $meta]; + }elseif(isset($simpleMappings[$stringId])){ + $this->simpleCoreToNetMapping[$simpleMappings[$stringId]] = $netId; + $this->simpleNetToCoreMapping[$netId] = $simpleMappings[$stringId]; + }elseif($stringId !== "minecraft:unknown"){ + throw new \InvalidArgumentException("Unmapped entry " . $stringId); + } + } + } + + /** + * @return int[] + * @phpstan-return array{int, int} + */ + public function toNetworkId(int $internalId, int $internalMeta) : array{ + if(isset($this->complexCoreToNetMapping[$internalId][$internalMeta])){ + return [$this->complexCoreToNetMapping[$internalId][$internalMeta], 0]; + } + if(array_key_exists($internalId, $this->simpleCoreToNetMapping)){ + return [$this->simpleCoreToNetMapping[$internalId], $internalMeta]; + } + + throw new \InvalidArgumentException("Unmapped ID/metadata combination $internalId:$internalMeta"); + } + + /** + * @return int[] + * @phpstan-return array{int, int} + */ + public function fromNetworkId(int $networkId, int $networkMeta, ?bool &$isComplexMapping = null) : array{ + if(isset($this->complexNetToCoreMapping[$networkId])){ + if($networkMeta !== 0){ + throw new \UnexpectedValueException("Unexpected non-zero network meta on complex item mapping"); + } + $isComplexMapping = true; + return $this->complexNetToCoreMapping[$networkId]; + } + $isComplexMapping = false; + if(isset($this->simpleNetToCoreMapping[$networkId])){ + return [$this->simpleNetToCoreMapping[$networkId], $networkMeta]; + } + throw new \UnexpectedValueException("Unmapped network ID/metadata combination $networkId:$networkMeta"); + } + + /** + * @return int[] + * @phpstan-return array{int, int} + */ + public function fromNetworkIdWithWildcardHandling(int $networkId, int $networkMeta) : array{ + $isComplexMapping = false; + if($networkMeta !== 0x7fff){ + return $this->fromNetworkId($networkId, $networkMeta); + } + [$id, $meta] = $this->fromNetworkId($networkId, 0, $isComplexMapping); + return [$id, $isComplexMapping ? $meta : -1]; + } +} diff --git a/src/pocketmine/network/mcpe/convert/ItemTypeDictionary.php b/src/pocketmine/network/mcpe/convert/ItemTypeDictionary.php new file mode 100644 index 000000000..b23983dc4 --- /dev/null +++ b/src/pocketmine/network/mcpe/convert/ItemTypeDictionary.php @@ -0,0 +1,106 @@ + + */ + private $itemTypes; + /** + * @var string[] + * @phpstan-var array + */ + private $intToStringIdMap = []; + /** + * @var int[] + * @phpstan-var array + */ + private $stringToIntMap = []; + + private static function make() : self{ + $data = file_get_contents(\pocketmine\RESOURCE_PATH . '/vanilla/required_item_list.json'); + if($data === false) throw new AssumptionFailedError("Missing required resource file"); + $table = json_decode($data, true); + if(!is_array($table)){ + throw new AssumptionFailedError("Invalid item list format"); + } + + $params = []; + foreach($table as $name => $entry){ + if(!is_array($entry) || !is_string($name) || !isset($entry["component_based"], $entry["runtime_id"]) || !is_bool($entry["component_based"]) || !is_int($entry["runtime_id"])){ + throw new AssumptionFailedError("Invalid item list format"); + } + $params[] = new ItemTypeEntry($name, $entry["runtime_id"], $entry["component_based"]); + } + return new self($params); + } + + /** + * @param ItemTypeEntry[] $itemTypes + */ + public function __construct(array $itemTypes){ + $this->itemTypes = $itemTypes; + foreach($this->itemTypes as $type){ + $this->stringToIntMap[$type->getStringId()] = $type->getNumericId(); + $this->intToStringIdMap[$type->getNumericId()] = $type->getStringId(); + } + } + + /** + * @return ItemTypeEntry[] + * @phpstan-return list + */ + public function getEntries() : array{ + return $this->itemTypes; + } + + public function fromStringId(string $stringId) : int{ + if(!array_key_exists($stringId, $this->stringToIntMap)){ + throw new \InvalidArgumentException("Unmapped string ID \"$stringId\""); + } + return $this->stringToIntMap[$stringId]; + } + + public function fromIntId(int $intId) : string{ + if(!array_key_exists($intId, $this->intToStringIdMap)){ + throw new \InvalidArgumentException("Unmapped int ID $intId"); + } + return $this->intToStringIdMap[$intId]; + } +} diff --git a/src/pocketmine/network/mcpe/convert/RuntimeBlockMapping.php b/src/pocketmine/network/mcpe/convert/RuntimeBlockMapping.php index 501a12d29..eba93c880 100644 --- a/src/pocketmine/network/mcpe/convert/RuntimeBlockMapping.php +++ b/src/pocketmine/network/mcpe/convert/RuntimeBlockMapping.php @@ -27,14 +27,10 @@ use pocketmine\block\BlockIds; use pocketmine\nbt\NBT; use pocketmine\nbt\NetworkLittleEndianNBTStream; use pocketmine\nbt\tag\CompoundTag; -use pocketmine\nbt\tag\ListTag; use pocketmine\network\mcpe\NetworkBinaryStream; +use pocketmine\utils\AssumptionFailedError; use function file_get_contents; -use function getmypid; use function json_decode; -use function mt_rand; -use function mt_srand; -use function shuffle; /** * @internal @@ -53,14 +49,16 @@ final class RuntimeBlockMapping{ } public static function init() : void{ - $tag = (new NetworkLittleEndianNBTStream())->read(file_get_contents(\pocketmine\RESOURCE_PATH . "vanilla/required_block_states.nbt")); - if(!($tag instanceof ListTag) or $tag->getTagType() !== NBT::TAG_Compound){ //this is a little redundant currently, but good for auto complete and makes phpstan happy - throw new \RuntimeException("Invalid blockstates table, expected TAG_List root"); + $canonicalBlockStatesFile = file_get_contents(\pocketmine\RESOURCE_PATH . "vanilla/canonical_block_states.nbt"); + if($canonicalBlockStatesFile === false){ + throw new AssumptionFailedError("Missing required resource file"); } - - /** @var CompoundTag[] $list */ - $list = $tag->getValue(); - self::$bedrockKnownStates = self::randomizeTable($list); + $stream = new NetworkBinaryStream($canonicalBlockStatesFile); + $list = []; + while(!$stream->feof()){ + $list[] = $stream->getNbtCompoundRoot(); + } + self::$bedrockKnownStates = $list; self::setupLegacyMappings(); } @@ -90,7 +88,7 @@ final class RuntimeBlockMapping{ */ $idToStatesMap = []; foreach(self::$bedrockKnownStates as $k => $state){ - $idToStatesMap[$state->getCompoundTag("block")->getString("name")][] = $k; + $idToStatesMap[$state->getString("name")][] = $k; } foreach($legacyStateMap as $pair){ $id = $legacyIdMap[$pair->getId()] ?? null; @@ -105,14 +103,14 @@ final class RuntimeBlockMapping{ $mappedState = $pair->getBlockState(); //TODO HACK: idiotic NBT compare behaviour on 3.x compares keys which are stored by values - $mappedState->setName("block"); + $mappedState->setName(""); $mappedName = $mappedState->getString("name"); if(!isset($idToStatesMap[$mappedName])){ throw new \RuntimeException("Mapped new state does not appear in network table"); } foreach($idToStatesMap[$mappedName] as $k){ $networkState = self::$bedrockKnownStates[$k]; - if($mappedState->equals($networkState->getCompoundTag("block"))){ + if($mappedState->equals($networkState)){ self::registerMapping($k, $id, $data); continue 2; } @@ -127,23 +125,6 @@ final class RuntimeBlockMapping{ } } - /** - * Randomizes the order of the runtimeID table to prevent plugins relying on them. - * Plugins shouldn't use this stuff anyway, but plugin devs have an irritating habit of ignoring what they - * aren't supposed to do, so we have to deliberately break it to make them stop. - * - * @param CompoundTag[] $table - * - * @return CompoundTag[] - */ - private static function randomizeTable(array $table) : array{ - $postSeed = mt_rand(); //save a seed to set afterwards, to avoid poor quality randoms - mt_srand(getmypid()); //Use a seed which is the same on all threads. This isn't a secure seed, but we don't care. - shuffle($table); - mt_srand($postSeed); //restore a good quality seed that isn't dependent on PID - return $table; - } - public static function toStaticRuntimeId(int $id, int $meta = 0) : int{ self::lazyInit(); /* diff --git a/src/pocketmine/network/mcpe/protocol/AnimateEntityPacket.php b/src/pocketmine/network/mcpe/protocol/AnimateEntityPacket.php new file mode 100644 index 000000000..18874081b --- /dev/null +++ b/src/pocketmine/network/mcpe/protocol/AnimateEntityPacket.php @@ -0,0 +1,108 @@ + + +use pocketmine\network\mcpe\NetworkSession; +use function count; + +class AnimateEntityPacket extends DataPacket/* implements ClientboundPacket*/{ + public const NETWORK_ID = ProtocolInfo::ANIMATE_ENTITY_PACKET; + + /** @var string */ + private $animation; + /** @var string */ + private $nextState; + /** @var string */ + private $stopExpression; + /** @var string */ + private $controller; + /** @var float */ + private $blendOutTime; + /** + * @var int[] + * @phpstan-var list + */ + private $actorRuntimeIds; + + /** + * @param int[] $actorRuntimeIds + * @phpstan-param list $actorRuntimeIds + */ + public static function create(string $animation, string $nextState, string $stopExpression, string $controller, float $blendOutTime, array $actorRuntimeIds) : self{ + $result = new self; + $result->animation = $animation; + $result->nextState = $nextState; + $result->stopExpression = $stopExpression; + $result->controller = $controller; + $result->blendOutTime = $blendOutTime; + $result->actorRuntimeIds = $actorRuntimeIds; + return $result; + } + + public function getAnimation() : string{ return $this->animation; } + + public function getNextState() : string{ return $this->nextState; } + + public function getStopExpression() : string{ return $this->stopExpression; } + + public function getController() : string{ return $this->controller; } + + public function getBlendOutTime() : float{ return $this->blendOutTime; } + + /** + * @return int[] + * @phpstan-return list + */ + public function getActorRuntimeIds() : array{ return $this->actorRuntimeIds; } + + protected function decodePayload() : void{ + $this->animation = $this->getString(); + $this->nextState = $this->getString(); + $this->stopExpression = $this->getString(); + $this->controller = $this->getString(); + $this->blendOutTime = $this->getLFloat(); + $this->actorRuntimeIds = []; + for($i = 0, $len = $this->getUnsignedVarInt(); $i < $len; ++$i){ + $this->actorRuntimeIds[] = $this->getEntityRuntimeId(); + } + } + + protected function encodePayload() : void{ + $this->putString($this->animation); + $this->putString($this->nextState); + $this->putString($this->stopExpression); + $this->putString($this->controller); + $this->putLFloat($this->blendOutTime); + $this->putUnsignedVarInt(count($this->actorRuntimeIds)); + foreach($this->actorRuntimeIds as $id){ + $this->putEntityRuntimeId($id); + } + } + + public function handle(NetworkSession $handler) : bool{ + return $handler->handleAnimateEntity($this); + } +} diff --git a/src/pocketmine/network/mcpe/protocol/CameraShakePacket.php b/src/pocketmine/network/mcpe/protocol/CameraShakePacket.php new file mode 100644 index 000000000..553912abb --- /dev/null +++ b/src/pocketmine/network/mcpe/protocol/CameraShakePacket.php @@ -0,0 +1,72 @@ + + +use pocketmine\network\mcpe\NetworkSession; + +class CameraShakePacket extends DataPacket/* implements ClientboundPacket*/{ + public const NETWORK_ID = ProtocolInfo::CAMERA_SHAKE_PACKET; + + public const TYPE_POSITIONAL = 0; + public const TYPE_ROTATIONAL = 1; + + /** @var float */ + private $intensity; + /** @var float */ + private $duration; + /** @var int */ + private $shakeType; + + public static function create(float $intensity, float $duration, int $shakeType) : self{ + $result = new self; + $result->intensity = $intensity; + $result->duration = $duration; + $result->shakeType = $shakeType; + return $result; + } + + public function getIntensity() : float{ return $this->intensity; } + + public function getDuration() : float{ return $this->duration; } + + public function getShakeType() : int{ return $this->shakeType; } + + protected function decodePayload() : void{ + $this->intensity = $this->getLFloat(); + $this->duration = $this->getLFloat(); + $this->shakeType = $this->getByte(); + } + + protected function encodePayload() : void{ + $this->putLFloat($this->intensity); + $this->putLFloat($this->duration); + $this->putByte($this->shakeType); + } + + public function handle(NetworkSession $handler) : bool{ + return $handler->handleCameraShake($this); + } +} diff --git a/src/pocketmine/network/mcpe/protocol/ContainerClosePacket.php b/src/pocketmine/network/mcpe/protocol/ContainerClosePacket.php index 2c35cf413..252f37769 100644 --- a/src/pocketmine/network/mcpe/protocol/ContainerClosePacket.php +++ b/src/pocketmine/network/mcpe/protocol/ContainerClosePacket.php @@ -32,13 +32,17 @@ class ContainerClosePacket extends DataPacket{ /** @var int */ public $windowId; + /** @var bool */ + public $server = false; protected function decodePayload(){ $this->windowId = $this->getByte(); + $this->server = $this->getBool(); } protected function encodePayload(){ $this->putByte($this->windowId); + $this->putBool($this->server); } public function handle(NetworkSession $session) : bool{ diff --git a/src/pocketmine/network/mcpe/protocol/CorrectPlayerMovePredictionPacket.php b/src/pocketmine/network/mcpe/protocol/CorrectPlayerMovePredictionPacket.php new file mode 100644 index 000000000..bc9af9903 --- /dev/null +++ b/src/pocketmine/network/mcpe/protocol/CorrectPlayerMovePredictionPacket.php @@ -0,0 +1,77 @@ + + +use pocketmine\math\Vector3; +use pocketmine\network\mcpe\NetworkSession; + +class CorrectPlayerMovePredictionPacket extends DataPacket/* implements ClientboundPacket*/{ + public const NETWORK_ID = ProtocolInfo::CORRECT_PLAYER_MOVE_PREDICTION_PACKET; + + /** @var Vector3 */ + private $position; + /** @var Vector3 */ + private $delta; + /** @var bool */ + private $onGround; + /** @var int */ + private $tick; + + public static function create(Vector3 $position, Vector3 $delta, bool $onGround, int $tick) : self{ + $result = new self; + $result->position = $position; + $result->delta = $delta; + $result->onGround = $onGround; + $result->tick = $tick; + return $result; + } + + public function getPosition() : Vector3{ return $this->position; } + + public function getDelta() : Vector3{ return $this->delta; } + + public function isOnGround() : bool{ return $this->onGround; } + + public function getTick() : int{ return $this->tick; } + + protected function decodePayload() : void{ + $this->position = $this->getVector3(); + $this->delta = $this->getVector3(); + $this->onGround = $this->getBool(); + $this->tick = $this->getUnsignedVarLong(); + } + + protected function encodePayload() : void{ + $this->putVector3($this->position); + $this->putVector3($this->delta); + $this->putBool($this->onGround); + $this->putUnsignedVarLong($this->tick); + } + + public function handle(NetworkSession $handler) : bool{ + return $handler->handleCorrectPlayerMovePrediction($this); + } +} diff --git a/src/pocketmine/network/mcpe/protocol/CraftingDataPacket.php b/src/pocketmine/network/mcpe/protocol/CraftingDataPacket.php index c035d1467..2401e862d 100644 --- a/src/pocketmine/network/mcpe/protocol/CraftingDataPacket.php +++ b/src/pocketmine/network/mcpe/protocol/CraftingDataPacket.php @@ -30,6 +30,7 @@ use pocketmine\inventory\ShapedRecipe; use pocketmine\inventory\ShapelessRecipe; use pocketmine\item\Item; use pocketmine\item\ItemFactory; +use pocketmine\network\mcpe\convert\ItemTranslator; use pocketmine\network\mcpe\NetworkBinaryStream; use pocketmine\network\mcpe\NetworkSession; use pocketmine\network\mcpe\protocol\types\PotionContainerChangeRecipe; @@ -124,13 +125,12 @@ class CraftingDataPacket extends DataPacket{ break; case self::ENTRY_FURNACE: case self::ENTRY_FURNACE_DATA: - $inputId = $this->getVarInt(); - $inputData = -1; - if($recipeType === self::ENTRY_FURNACE_DATA){ - $inputData = $this->getVarInt(); - if($inputData === 0x7fff){ - $inputData = -1; - } + $inputIdNet = $this->getVarInt(); + if($recipeType === self::ENTRY_FURNACE){ + [$inputId, $inputData] = ItemTranslator::getInstance()->fromNetworkIdWithWildcardHandling($inputIdNet, 0x7fff); + }else{ + $inputMetaNet = $this->getVarInt(); + [$inputId, $inputData] = ItemTranslator::getInstance()->fromNetworkIdWithWildcardHandling($inputIdNet, $inputMetaNet); } $entry["input"] = ItemFactory::get($inputId, $inputData); $entry["output"] = $out = $this->getSlot(); @@ -150,18 +150,25 @@ class CraftingDataPacket extends DataPacket{ $this->decodedEntries[] = $entry; } for($i = 0, $count = $this->getUnsignedVarInt(); $i < $count; ++$i){ - $input = $this->getVarInt(); - $inputMeta = $this->getVarInt(); - $ingredient = $this->getVarInt(); - $ingredientMeta = $this->getVarInt(); - $output = $this->getVarInt(); - $outputMeta = $this->getVarInt(); + $inputIdNet = $this->getVarInt(); + $inputMetaNet = $this->getVarInt(); + [$input, $inputMeta] = ItemTranslator::getInstance()->fromNetworkId($inputIdNet, $inputMetaNet); + $ingredientIdNet = $this->getVarInt(); + $ingredientMetaNet = $this->getVarInt(); + [$ingredient, $ingredientMeta] = ItemTranslator::getInstance()->fromNetworkId($ingredientIdNet, $ingredientMetaNet); + $outputIdNet = $this->getVarInt(); + $outputMetaNet = $this->getVarInt(); + [$output, $outputMeta] = ItemTranslator::getInstance()->fromNetworkId($outputIdNet, $outputMetaNet); $this->potionTypeRecipes[] = new PotionTypeRecipe($input, $inputMeta, $ingredient, $ingredientMeta, $output, $outputMeta); } for($i = 0, $count = $this->getUnsignedVarInt(); $i < $count; ++$i){ - $input = $this->getVarInt(); - $ingredient = $this->getVarInt(); - $output = $this->getVarInt(); + //TODO: we discard inbound ID here, not safe because netID on its own might map to internalID+internalMeta for us + $inputIdNet = $this->getVarInt(); + [$input, ] = ItemTranslator::getInstance()->fromNetworkId($inputIdNet, 0); + $ingredientIdNet = $this->getVarInt(); + [$ingredient, ] = ItemTranslator::getInstance()->fromNetworkId($ingredientIdNet, 0); + $outputIdNet = $this->getVarInt(); + [$output, ] = ItemTranslator::getInstance()->fromNetworkId($outputIdNet, 0); $this->potionContainerRecipes[] = new PotionContainerChangeRecipe($input, $ingredient, $output); } $this->cleanRecipes = $this->getBool(); @@ -230,15 +237,18 @@ class CraftingDataPacket extends DataPacket{ } private static function writeFurnaceRecipe(FurnaceRecipe $recipe, NetworkBinaryStream $stream) : int{ - $stream->putVarInt($recipe->getInput()->getId()); - $result = CraftingDataPacket::ENTRY_FURNACE; - if(!$recipe->getInput()->hasAnyDamageValue()){ //Data recipe - $stream->putVarInt($recipe->getInput()->getDamage()); - $result = CraftingDataPacket::ENTRY_FURNACE_DATA; + $input = $recipe->getInput(); + if($input->hasAnyDamageValue()){ + [$netId, ] = ItemTranslator::getInstance()->toNetworkId($input->getId(), 0); + $netData = 0x7fff; + }else{ + [$netId, $netData] = ItemTranslator::getInstance()->toNetworkId($input->getId(), $input->getDamage()); } + $stream->putVarInt($netId); + $stream->putVarInt($netData); $stream->putSlot($recipe->getResult()); $stream->putString("furnace"); //TODO: blocktype (no prefix) (this might require internal API breaks) - return $result; + return CraftingDataPacket::ENTRY_FURNACE_DATA; } /** diff --git a/src/pocketmine/network/mcpe/protocol/ItemComponentPacket.php b/src/pocketmine/network/mcpe/protocol/ItemComponentPacket.php new file mode 100644 index 000000000..26a7ac9dd --- /dev/null +++ b/src/pocketmine/network/mcpe/protocol/ItemComponentPacket.php @@ -0,0 +1,78 @@ + + +use pocketmine\nbt\NetworkLittleEndianNBTStream; +use pocketmine\network\mcpe\NetworkSession; +use pocketmine\network\mcpe\protocol\types\ItemComponentPacketEntry; +use function count; + +class ItemComponentPacket extends DataPacket/* implements ClientboundPacket*/{ + public const NETWORK_ID = ProtocolInfo::ITEM_COMPONENT_PACKET; + + /** + * @var ItemComponentPacketEntry[] + * @phpstan-var list + */ + private $entries; + + /** + * @param ItemComponentPacketEntry[] $entries + * @phpstan-param list $entries + */ + public static function create(array $entries) : self{ + $result = new self; + $result->entries = $entries; + return $result; + } + + /** + * @return ItemComponentPacketEntry[] + * @phpstan-return list + */ + public function getEntries() : array{ return $this->entries; } + + protected function decodePayload() : void{ + $this->entries = []; + for($i = 0, $len = $this->getUnsignedVarInt(); $i < $len; ++$i){ + $name = $this->getString(); + $nbt = $this->getNbtCompoundRoot(); + $this->entries[] = new ItemComponentPacketEntry($name, $nbt); + } + } + + protected function encodePayload() : void{ + $this->putUnsignedVarInt(count($this->entries)); + foreach($this->entries as $entry){ + $this->putString($entry->getName()); + $this->put((new NetworkLittleEndianNBTStream())->write($entry->getComponentNbt())); + } + } + + public function handle(NetworkSession $handler) : bool{ + return $handler->handleItemComponent($this); + } +} diff --git a/src/pocketmine/network/mcpe/protocol/MotionPredictionHintsPacket.php b/src/pocketmine/network/mcpe/protocol/MotionPredictionHintsPacket.php new file mode 100644 index 000000000..4b3607553 --- /dev/null +++ b/src/pocketmine/network/mcpe/protocol/MotionPredictionHintsPacket.php @@ -0,0 +1,70 @@ + + +use pocketmine\math\Vector3; +use pocketmine\network\mcpe\NetworkSession; + +class MotionPredictionHintsPacket extends DataPacket/* implements ClientboundPacket*/{ + public const NETWORK_ID = ProtocolInfo::MOTION_PREDICTION_HINTS_PACKET; + + /** @var int */ + private $entityRuntimeId; + /** @var Vector3 */ + private $motion; + /** @var bool */ + private $onGround; + + public static function create(int $entityRuntimeId, Vector3 $motion, bool $onGround) : self{ + $result = new self; + $result->entityRuntimeId = $entityRuntimeId; + $result->motion = $motion; + $result->onGround = $onGround; + return $result; + } + + public function getEntityRuntimeIdField() : int{ return $this->entityRuntimeId; } //TODO: rename this on PM4 (crap architecture, thanks shoghi) + + public function getMotion() : Vector3{ return $this->motion; } + + public function isOnGround() : bool{ return $this->onGround; } + + protected function decodePayload() : void{ + $this->entityRuntimeId = $this->getEntityRuntimeId(); + $this->motion = $this->getVector3(); + $this->onGround = $this->getBool(); + } + + protected function encodePayload() : void{ + $this->putEntityRuntimeId($this->entityRuntimeId); + $this->putVector3($this->motion); + $this->putBool($this->onGround); + } + + public function handle(NetworkSession $handler) : bool{ + return $handler->handleMotionPredictionHints($this); + } +} diff --git a/src/pocketmine/network/mcpe/protocol/MoveActorDeltaPacket.php b/src/pocketmine/network/mcpe/protocol/MoveActorDeltaPacket.php index ffae8449f..d3d257323 100644 --- a/src/pocketmine/network/mcpe/protocol/MoveActorDeltaPacket.php +++ b/src/pocketmine/network/mcpe/protocol/MoveActorDeltaPacket.php @@ -44,12 +44,12 @@ class MoveActorDeltaPacket extends DataPacket{ public $entityRuntimeId; /** @var int */ public $flags; - /** @var int */ - public $xDiff = 0; - /** @var int */ - public $yDiff = 0; - /** @var int */ - public $zDiff = 0; + /** @var float */ + public $xPos = 0; + /** @var float */ + public $yPos = 0; + /** @var float */ + public $zPos = 0; /** @var float */ public $xRot = 0.0; /** @var float */ @@ -57,9 +57,9 @@ class MoveActorDeltaPacket extends DataPacket{ /** @var float */ public $zRot = 0.0; - private function maybeReadCoord(int $flag) : int{ + private function maybeReadCoord(int $flag) : float{ if(($this->flags & $flag) !== 0){ - return $this->getVarInt(); + return $this->getLFloat(); } return 0; } @@ -74,17 +74,17 @@ class MoveActorDeltaPacket extends DataPacket{ protected function decodePayload(){ $this->entityRuntimeId = $this->getEntityRuntimeId(); $this->flags = $this->getLShort(); - $this->xDiff = $this->maybeReadCoord(self::FLAG_HAS_X); - $this->yDiff = $this->maybeReadCoord(self::FLAG_HAS_Y); - $this->zDiff = $this->maybeReadCoord(self::FLAG_HAS_Z); + $this->xPos = $this->maybeReadCoord(self::FLAG_HAS_X); + $this->yPos = $this->maybeReadCoord(self::FLAG_HAS_Y); + $this->zPos = $this->maybeReadCoord(self::FLAG_HAS_Z); $this->xRot = $this->maybeReadRotation(self::FLAG_HAS_ROT_X); $this->yRot = $this->maybeReadRotation(self::FLAG_HAS_ROT_Y); $this->zRot = $this->maybeReadRotation(self::FLAG_HAS_ROT_Z); } - private function maybeWriteCoord(int $flag, int $val) : void{ + private function maybeWriteCoord(int $flag, float $val) : void{ if(($this->flags & $flag) !== 0){ - $this->putVarInt($val); + $this->putLFloat($val); } } @@ -97,9 +97,9 @@ class MoveActorDeltaPacket extends DataPacket{ protected function encodePayload(){ $this->putEntityRuntimeId($this->entityRuntimeId); $this->putLShort($this->flags); - $this->maybeWriteCoord(self::FLAG_HAS_X, $this->xDiff); - $this->maybeWriteCoord(self::FLAG_HAS_Y, $this->yDiff); - $this->maybeWriteCoord(self::FLAG_HAS_Z, $this->zDiff); + $this->maybeWriteCoord(self::FLAG_HAS_X, $this->xPos); + $this->maybeWriteCoord(self::FLAG_HAS_Y, $this->yPos); + $this->maybeWriteCoord(self::FLAG_HAS_Z, $this->zPos); $this->maybeWriteRotation(self::FLAG_HAS_ROT_X, $this->xRot); $this->maybeWriteRotation(self::FLAG_HAS_ROT_Y, $this->yRot); $this->maybeWriteRotation(self::FLAG_HAS_ROT_Z, $this->zRot); diff --git a/src/pocketmine/network/mcpe/protocol/MovePlayerPacket.php b/src/pocketmine/network/mcpe/protocol/MovePlayerPacket.php index 5f318e968..7fa8eb1e3 100644 --- a/src/pocketmine/network/mcpe/protocol/MovePlayerPacket.php +++ b/src/pocketmine/network/mcpe/protocol/MovePlayerPacket.php @@ -56,6 +56,8 @@ class MovePlayerPacket extends DataPacket{ public $teleportCause = 0; /** @var int */ public $teleportItem = 0; + /** @var int */ + public $tick = 0; protected function decodePayload(){ $this->entityRuntimeId = $this->getEntityRuntimeId(); @@ -70,6 +72,7 @@ class MovePlayerPacket extends DataPacket{ $this->teleportCause = $this->getLInt(); $this->teleportItem = $this->getLInt(); } + $this->tick = $this->getUnsignedVarLong(); } protected function encodePayload(){ @@ -85,6 +88,7 @@ class MovePlayerPacket extends DataPacket{ $this->putLInt($this->teleportCause); $this->putLInt($this->teleportItem); } + $this->putUnsignedVarLong($this->tick); } public function handle(NetworkSession $session) : bool{ diff --git a/src/pocketmine/network/mcpe/protocol/PacketPool.php b/src/pocketmine/network/mcpe/protocol/PacketPool.php index f42a2bc57..f46cbc80f 100644 --- a/src/pocketmine/network/mcpe/protocol/PacketPool.php +++ b/src/pocketmine/network/mcpe/protocol/PacketPool.php @@ -71,7 +71,6 @@ class PacketPool{ static::registerPacket(new BlockPickRequestPacket()); static::registerPacket(new ActorPickRequestPacket()); static::registerPacket(new PlayerActionPacket()); - static::registerPacket(new ActorFallPacket()); static::registerPacket(new HurtArmorPacket()); static::registerPacket(new SetActorDataPacket()); static::registerPacket(new SetActorMotionPacket()); @@ -166,7 +165,6 @@ class PacketPool{ static::registerPacket(new MapCreateLockedCopyPacket()); static::registerPacket(new StructureTemplateDataRequestPacket()); static::registerPacket(new StructureTemplateDataResponsePacket()); - static::registerPacket(new UpdateBlockPropertiesPacket()); static::registerPacket(new ClientCacheBlobStatusPacket()); static::registerPacket(new ClientCacheMissResponsePacket()); static::registerPacket(new EducationSettingsPacket()); @@ -189,6 +187,12 @@ class PacketPool{ static::registerPacket(new PositionTrackingDBClientRequestPacket()); static::registerPacket(new DebugInfoPacket()); static::registerPacket(new PacketViolationWarningPacket()); + static::registerPacket(new MotionPredictionHintsPacket()); + static::registerPacket(new AnimateEntityPacket()); + static::registerPacket(new CameraShakePacket()); + static::registerPacket(new PlayerFogPacket()); + static::registerPacket(new CorrectPlayerMovePredictionPacket()); + static::registerPacket(new ItemComponentPacket()); } /** diff --git a/src/pocketmine/network/mcpe/protocol/PlayerActionPacket.php b/src/pocketmine/network/mcpe/protocol/PlayerActionPacket.php index 12734cc4e..f6fb8d053 100644 --- a/src/pocketmine/network/mcpe/protocol/PlayerActionPacket.php +++ b/src/pocketmine/network/mcpe/protocol/PlayerActionPacket.php @@ -43,7 +43,7 @@ class PlayerActionPacket extends DataPacket{ public const ACTION_STOP_SPRINT = 10; public const ACTION_START_SNEAK = 11; public const ACTION_STOP_SNEAK = 12; - public const ACTION_DIMENSION_CHANGE_REQUEST = 13; //sent when dying in different dimension + public const ACTION_CREATIVE_PLAYER_DESTROY_BLOCK = 13; public const ACTION_DIMENSION_CHANGE_ACK = 14; //sent when spawning in a different dimension to tell the server we spawned public const ACTION_START_GLIDE = 15; public const ACTION_STOP_GLIDE = 16; diff --git a/src/pocketmine/network/mcpe/protocol/PlayerAuthInputPacket.php b/src/pocketmine/network/mcpe/protocol/PlayerAuthInputPacket.php index e8900dd7f..cee616bd1 100644 --- a/src/pocketmine/network/mcpe/protocol/PlayerAuthInputPacket.php +++ b/src/pocketmine/network/mcpe/protocol/PlayerAuthInputPacket.php @@ -54,13 +54,17 @@ class PlayerAuthInputPacket extends DataPacket/* implements ServerboundPacket*/{ private $playMode; /** @var Vector3|null */ private $vrGazeDirection = null; + /** @var int */ + private $tick; + /** @var Vector3 */ + private $delta; /** * @param int $inputMode @see InputMode * @param int $playMode @see PlayMode * @param Vector3|null $vrGazeDirection only used when PlayMode::VR */ - public static function create(Vector3 $position, float $pitch, float $yaw, float $headYaw, float $moveVecX, float $moveVecZ, int $inputFlags, int $inputMode, int $playMode, ?Vector3 $vrGazeDirection = null) : self{ + public static function create(Vector3 $position, float $pitch, float $yaw, float $headYaw, float $moveVecX, float $moveVecZ, int $inputFlags, int $inputMode, int $playMode, ?Vector3 $vrGazeDirection, int $tick, Vector3 $delta) : self{ if($playMode === PlayMode::VR and $vrGazeDirection === null){ //yuck, can we get a properly written packet just once? ... throw new \InvalidArgumentException("Gaze direction must be provided for VR play mode"); @@ -78,6 +82,8 @@ class PlayerAuthInputPacket extends DataPacket/* implements ServerboundPacket*/{ if($vrGazeDirection !== null){ $result->vrGazeDirection = $vrGazeDirection->asVector3(); } + $result->tick = $tick; + $result->delta = $delta; return $result; } @@ -127,6 +133,10 @@ class PlayerAuthInputPacket extends DataPacket/* implements ServerboundPacket*/{ return $this->vrGazeDirection; } + public function getTick() : int{ return $this->tick; } + + public function getDelta() : Vector3{ return $this->delta; } + protected function decodePayload() : void{ $this->pitch = $this->getLFloat(); $this->yaw = $this->getLFloat(); @@ -140,6 +150,8 @@ class PlayerAuthInputPacket extends DataPacket/* implements ServerboundPacket*/{ if($this->playMode === PlayMode::VR){ $this->vrGazeDirection = $this->getVector3(); } + $this->tick = $this->getUnsignedVarLong(); + $this->delta = $this->getVector3(); } protected function encodePayload() : void{ @@ -156,6 +168,8 @@ class PlayerAuthInputPacket extends DataPacket/* implements ServerboundPacket*/{ assert($this->vrGazeDirection !== null); $this->putVector3($this->vrGazeDirection); } + $this->putUnsignedVarLong($this->tick); + $this->putVector3($this->delta); } public function handle(NetworkSession $handler) : bool{ diff --git a/src/pocketmine/network/mcpe/protocol/UpdateBlockPropertiesPacket.php b/src/pocketmine/network/mcpe/protocol/PlayerFogPacket.php similarity index 55% rename from src/pocketmine/network/mcpe/protocol/UpdateBlockPropertiesPacket.php rename to src/pocketmine/network/mcpe/protocol/PlayerFogPacket.php index f29d2b464..19f0b0fb2 100644 --- a/src/pocketmine/network/mcpe/protocol/UpdateBlockPropertiesPacket.php +++ b/src/pocketmine/network/mcpe/protocol/PlayerFogPacket.php @@ -25,31 +25,49 @@ namespace pocketmine\network\mcpe\protocol; #include -use pocketmine\nbt\NetworkLittleEndianNBTStream; -use pocketmine\nbt\tag\CompoundTag; use pocketmine\network\mcpe\NetworkSession; +use function count; -class UpdateBlockPropertiesPacket extends DataPacket{ - public const NETWORK_ID = ProtocolInfo::UPDATE_BLOCK_PROPERTIES_PACKET; +class PlayerFogPacket extends DataPacket/* implements ClientboundPacket*/{ + public const NETWORK_ID = ProtocolInfo::PLAYER_FOG_PACKET; - /** @var string */ - private $nbt; + /** + * @var string[] + * @phpstan-var list + */ + private $fogLayers; - public static function create(CompoundTag $data) : self{ + /** + * @param string[] $fogLayers + * @phpstan-param list $fogLayers + */ + public static function create(array $fogLayers) : self{ $result = new self; - $result->nbt = (new NetworkLittleEndianNBTStream())->write($data); + $result->fogLayers = $fogLayers; return $result; } + /** + * @return string[] + * @phpstan-return list + */ + public function getFogLayers() : array{ return $this->fogLayers; } + protected function decodePayload() : void{ - $this->nbt = $this->getRemaining(); + $this->fogLayers = []; + for($i = 0, $len = $this->getUnsignedVarInt(); $i < $len; ++$i){ + $this->fogLayers[] = $this->getString(); + } } protected function encodePayload() : void{ - $this->put($this->nbt); + $this->putUnsignedVarInt(count($this->fogLayers)); + foreach($this->fogLayers as $fogLayer){ + $this->putString($fogLayer); + } } public function handle(NetworkSession $handler) : bool{ - return $handler->handleUpdateBlockProperties($this); + return $handler->handlePlayerFog($this); } } diff --git a/src/pocketmine/network/mcpe/protocol/ProtocolInfo.php b/src/pocketmine/network/mcpe/protocol/ProtocolInfo.php index 5a326e198..d186a0f12 100644 --- a/src/pocketmine/network/mcpe/protocol/ProtocolInfo.php +++ b/src/pocketmine/network/mcpe/protocol/ProtocolInfo.php @@ -37,11 +37,11 @@ interface ProtocolInfo{ */ /** Actual Minecraft: PE protocol version */ - public const CURRENT_PROTOCOL = 408; + public const CURRENT_PROTOCOL = 419; /** Current Minecraft PE version reported by the server. This is usually the earliest currently supported version. */ - public const MINECRAFT_VERSION = 'v1.16.20'; + public const MINECRAFT_VERSION = 'v1.16.100'; /** Version number sent to clients in ping responses. */ - public const MINECRAFT_VERSION_NETWORK = '1.16.20'; + public const MINECRAFT_VERSION_NETWORK = '1.16.100'; public const LOGIN_PACKET = 0x01; public const PLAY_STATUS_PACKET = 0x02; @@ -79,7 +79,7 @@ interface ProtocolInfo{ public const BLOCK_PICK_REQUEST_PACKET = 0x22; public const ACTOR_PICK_REQUEST_PACKET = 0x23; public const PLAYER_ACTION_PACKET = 0x24; - public const ACTOR_FALL_PACKET = 0x25; + public const HURT_ARMOR_PACKET = 0x26; public const SET_ACTOR_DATA_PACKET = 0x27; public const SET_ACTOR_MOTION_PACKET = 0x28; @@ -176,7 +176,7 @@ interface ProtocolInfo{ public const MAP_CREATE_LOCKED_COPY_PACKET = 0x83; public const STRUCTURE_TEMPLATE_DATA_REQUEST_PACKET = 0x84; public const STRUCTURE_TEMPLATE_DATA_RESPONSE_PACKET = 0x85; - public const UPDATE_BLOCK_PROPERTIES_PACKET = 0x86; + public const CLIENT_CACHE_BLOB_STATUS_PACKET = 0x87; public const CLIENT_CACHE_MISS_RESPONSE_PACKET = 0x88; public const EDUCATION_SETTINGS_PACKET = 0x89; @@ -199,5 +199,11 @@ interface ProtocolInfo{ public const POSITION_TRACKING_D_B_CLIENT_REQUEST_PACKET = 0x9a; public const DEBUG_INFO_PACKET = 0x9b; public const PACKET_VIOLATION_WARNING_PACKET = 0x9c; + public const MOTION_PREDICTION_HINTS_PACKET = 0x9d; + public const ANIMATE_ENTITY_PACKET = 0x9e; + public const CAMERA_SHAKE_PACKET = 0x9f; + public const PLAYER_FOG_PACKET = 0xa0; + public const CORRECT_PLAYER_MOVE_PREDICTION_PACKET = 0xa1; + public const ITEM_COMPONENT_PACKET = 0xa2; } diff --git a/src/pocketmine/network/mcpe/protocol/ResourcePackStackPacket.php b/src/pocketmine/network/mcpe/protocol/ResourcePackStackPacket.php index 5d1835675..58a676aee 100644 --- a/src/pocketmine/network/mcpe/protocol/ResourcePackStackPacket.php +++ b/src/pocketmine/network/mcpe/protocol/ResourcePackStackPacket.php @@ -26,6 +26,7 @@ namespace pocketmine\network\mcpe\protocol; #include use pocketmine\network\mcpe\NetworkSession; +use pocketmine\network\mcpe\protocol\types\Experiments; use pocketmine\resourcepacks\ResourcePack; use function count; @@ -40,11 +41,12 @@ class ResourcePackStackPacket extends DataPacket{ /** @var ResourcePack[] */ public $resourcePackStack = []; - /** @var bool */ - public $isExperimental = false; /** @var string */ public $baseGameVersion = ProtocolInfo::MINECRAFT_VERSION_NETWORK; + /** @var Experiments */ + public $experiments; + protected function decodePayload(){ $this->mustAccept = $this->getBool(); $behaviorPackCount = $this->getUnsignedVarInt(); @@ -61,8 +63,8 @@ class ResourcePackStackPacket extends DataPacket{ $this->getString(); } - $this->isExperimental = $this->getBool(); $this->baseGameVersion = $this->getString(); + $this->experiments = Experiments::read($this); } protected function encodePayload(){ @@ -82,8 +84,8 @@ class ResourcePackStackPacket extends DataPacket{ $this->putString(""); //TODO: subpack name } - $this->putBool($this->isExperimental); $this->putString($this->baseGameVersion); + $this->experiments->write($this); } public function handle(NetworkSession $session) : bool{ diff --git a/src/pocketmine/network/mcpe/protocol/SetActorDataPacket.php b/src/pocketmine/network/mcpe/protocol/SetActorDataPacket.php index c48c86420..0e05562dd 100644 --- a/src/pocketmine/network/mcpe/protocol/SetActorDataPacket.php +++ b/src/pocketmine/network/mcpe/protocol/SetActorDataPacket.php @@ -38,14 +38,19 @@ class SetActorDataPacket extends DataPacket{ */ public $metadata; + /** @var int */ + public $tick = 0; + protected function decodePayload(){ $this->entityRuntimeId = $this->getEntityRuntimeId(); $this->metadata = $this->getEntityMetadata(); + $this->tick = $this->getUnsignedVarLong(); } protected function encodePayload(){ $this->putEntityRuntimeId($this->entityRuntimeId); $this->putEntityMetadata($this->metadata); + $this->putUnsignedVarLong($this->tick); } public function handle(NetworkSession $session) : bool{ diff --git a/src/pocketmine/network/mcpe/protocol/StartGamePacket.php b/src/pocketmine/network/mcpe/protocol/StartGamePacket.php index c457d77e0..bf0d993c6 100644 --- a/src/pocketmine/network/mcpe/protocol/StartGamePacket.php +++ b/src/pocketmine/network/mcpe/protocol/StartGamePacket.php @@ -27,29 +27,22 @@ namespace pocketmine\network\mcpe\protocol; use pocketmine\math\Vector3; use pocketmine\nbt\NetworkLittleEndianNBTStream; -use pocketmine\nbt\tag\ListTag; -use pocketmine\network\mcpe\convert\RuntimeBlockMapping; -use pocketmine\network\mcpe\NetworkBinaryStream; use pocketmine\network\mcpe\NetworkSession; +use pocketmine\network\mcpe\protocol\types\BlockPaletteEntry; use pocketmine\network\mcpe\protocol\types\EducationEditionOffer; +use pocketmine\network\mcpe\protocol\types\Experiments; use pocketmine\network\mcpe\protocol\types\GameRuleType; use pocketmine\network\mcpe\protocol\types\GeneratorType; +use pocketmine\network\mcpe\protocol\types\ItemTypeEntry; use pocketmine\network\mcpe\protocol\types\MultiplayerGameVisibility; +use pocketmine\network\mcpe\protocol\types\PlayerMovementType; use pocketmine\network\mcpe\protocol\types\PlayerPermissions; use pocketmine\network\mcpe\protocol\types\SpawnSettings; use function count; -use function file_get_contents; -use function json_decode; -use const pocketmine\RESOURCE_PATH; class StartGamePacket extends DataPacket{ public const NETWORK_ID = ProtocolInfo::START_GAME_PACKET; - /** @var string|null */ - private static $blockTableCache = null; - /** @var string|null */ - private static $itemTableCache = null; - /** @var int */ public $entityUniqueId; /** @var int */ @@ -116,6 +109,8 @@ class StartGamePacket extends DataPacket{ public $gameRules = [ //TODO: implement this "naturalregeneration" => [GameRuleType::BOOL, false] //Hack for client side regeneration ]; + /** @var Experiments */ + public $experiments; /** @var bool */ public $hasBonusChestEnabled = false; /** @var bool */ @@ -159,8 +154,8 @@ class StartGamePacket extends DataPacket{ public $premiumWorldTemplateId = ""; /** @var bool */ public $isTrial = false; - /** @var bool */ - public $isMovementServerAuthoritative = false; + /** @var int */ + public $playerMovementType = PlayerMovementType::LEGACY; /** @var int */ public $currentTick = 0; //only used if isTrial is true /** @var int */ @@ -168,13 +163,17 @@ class StartGamePacket extends DataPacket{ /** @var string */ public $multiplayerCorrelationId = ""; //TODO: this should be filled with a UUID of some sort - /** @var ListTag|null */ - public $blockTable = null; /** - * @var int[]|null string (name) => int16 (legacyID) - * @phpstan-var array|null + * @var BlockPaletteEntry[] + * @phpstan-var list */ - public $itemTable = null; + public $blockPalette = []; + + /** + * @var ItemTypeEntry[] + * @phpstan-var list + */ + public $itemTable; /** @var bool */ public $enableNewInventorySystem = false; //TODO @@ -210,6 +209,7 @@ class StartGamePacket extends DataPacket{ $this->commandsEnabled = $this->getBool(); $this->isTexturePacksRequired = $this->getBool(); $this->gameRules = $this->getGameRules(); + $this->experiments = Experiments::read($this); $this->hasBonusChestEnabled = $this->getBool(); $this->hasStartWithMapEnabled = $this->getBool(); $this->defaultPlayerPermission = $this->getVarInt(); @@ -235,23 +235,25 @@ class StartGamePacket extends DataPacket{ $this->worldName = $this->getString(); $this->premiumWorldTemplateId = $this->getString(); $this->isTrial = $this->getBool(); - $this->isMovementServerAuthoritative = $this->getBool(); + $this->playerMovementType = $this->getVarInt(); $this->currentTick = $this->getLLong(); $this->enchantmentSeed = $this->getVarInt(); - $blockTable = (new NetworkLittleEndianNBTStream())->read($this->buffer, false, $this->offset, 512); - if(!($blockTable instanceof ListTag)){ - throw new \UnexpectedValueException("Wrong block table root NBT tag type"); + $this->blockPalette = []; + for($i = 0, $len = $this->getUnsignedVarInt(); $i < $len; ++$i){ + $blockName = $this->getString(); + $state = $this->getNbtCompoundRoot(); + $this->blockPalette[] = new BlockPaletteEntry($blockName, $state); } - $this->blockTable = $blockTable; $this->itemTable = []; for($i = 0, $count = $this->getUnsignedVarInt(); $i < $count; ++$i){ - $id = $this->getString(); - $legacyId = $this->getSignedLShort(); + $stringId = $this->getString(); + $numericId = $this->getSignedLShort(); + $isComponentBased = $this->getBool(); - $this->itemTable[$id] = $legacyId; + $this->itemTable[] = new ItemTypeEntry($stringId, $numericId, $isComponentBased); } $this->multiplayerCorrelationId = $this->getString(); @@ -290,6 +292,7 @@ class StartGamePacket extends DataPacket{ $this->putBool($this->commandsEnabled); $this->putBool($this->isTexturePacksRequired); $this->putGameRules($this->gameRules); + $this->experiments->write($this); $this->putBool($this->hasBonusChestEnabled); $this->putBool($this->hasStartWithMapEnabled); $this->putVarInt($this->defaultPlayerPermission); @@ -314,47 +317,28 @@ class StartGamePacket extends DataPacket{ $this->putString($this->worldName); $this->putString($this->premiumWorldTemplateId); $this->putBool($this->isTrial); - $this->putBool($this->isMovementServerAuthoritative); + $this->putVarInt($this->playerMovementType); $this->putLLong($this->currentTick); $this->putVarInt($this->enchantmentSeed); - if($this->blockTable === null){ - if(self::$blockTableCache === null){ - //this is a really nasty hack, but it'll do for now - self::$blockTableCache = (new NetworkLittleEndianNBTStream())->write(new ListTag("", RuntimeBlockMapping::getBedrockKnownStates())); - } - $this->put(self::$blockTableCache); - }else{ - $this->put((new NetworkLittleEndianNBTStream())->write($this->blockTable)); + $this->putUnsignedVarInt(count($this->blockPalette)); + $nbtWriter = new NetworkLittleEndianNBTStream(); + foreach($this->blockPalette as $entry){ + $this->putString($entry->getName()); + $this->put($nbtWriter->write($entry->getStates())); } - if($this->itemTable === null){ - if(self::$itemTableCache === null){ - self::$itemTableCache = self::serializeItemTable(json_decode(file_get_contents(RESOURCE_PATH . '/vanilla/item_id_map.json'), true)); - } - $this->put(self::$itemTableCache); - }else{ - $this->put(self::serializeItemTable($this->itemTable)); + $this->putUnsignedVarInt(count($this->itemTable)); + foreach($this->itemTable as $entry){ + $this->putString($entry->getStringId()); + $this->putLShort($entry->getNumericId()); + $this->putBool($entry->isComponentBased()); } $this->putString($this->multiplayerCorrelationId); $this->putBool($this->enableNewInventorySystem); } - /** - * @param int[] $table - * @phpstan-param array $table - */ - private static function serializeItemTable(array $table) : string{ - $stream = new NetworkBinaryStream(); - $stream->putUnsignedVarInt(count($table)); - foreach($table as $name => $legacyId){ - $stream->putString($name); - $stream->putLShort($legacyId); - } - return $stream->getBuffer(); - } - public function handle(NetworkSession $session) : bool{ return $session->handleStartGame($this); } diff --git a/src/pocketmine/network/mcpe/protocol/UpdateAttributesPacket.php b/src/pocketmine/network/mcpe/protocol/UpdateAttributesPacket.php index 69d2d4be3..d42c24eb9 100644 --- a/src/pocketmine/network/mcpe/protocol/UpdateAttributesPacket.php +++ b/src/pocketmine/network/mcpe/protocol/UpdateAttributesPacket.php @@ -35,15 +35,19 @@ class UpdateAttributesPacket extends DataPacket{ public $entityRuntimeId; /** @var Attribute[] */ public $entries = []; + /** @var int */ + public $tick = 0; protected function decodePayload(){ $this->entityRuntimeId = $this->getEntityRuntimeId(); $this->entries = $this->getAttributeList(); + $this->tick = $this->getUnsignedVarLong(); } protected function encodePayload(){ $this->putEntityRuntimeId($this->entityRuntimeId); $this->putAttributeList(...$this->entries); + $this->putUnsignedVarLong($this->tick); } public function handle(NetworkSession $session) : bool{ diff --git a/src/pocketmine/network/mcpe/protocol/types/BlockPaletteEntry.php b/src/pocketmine/network/mcpe/protocol/types/BlockPaletteEntry.php new file mode 100644 index 000000000..27c5fb29e --- /dev/null +++ b/src/pocketmine/network/mcpe/protocol/types/BlockPaletteEntry.php @@ -0,0 +1,43 @@ +name = $name; + $this->states = $states; + } + + public function getName() : string{ return $this->name; } + + public function getStates() : CompoundTag{ return $this->states; } +} diff --git a/src/pocketmine/network/mcpe/protocol/types/Experiments.php b/src/pocketmine/network/mcpe/protocol/types/Experiments.php new file mode 100644 index 000000000..8c3196dbb --- /dev/null +++ b/src/pocketmine/network/mcpe/protocol/types/Experiments.php @@ -0,0 +1,72 @@ + + */ + private $experiments; + /** @var bool */ + private $hasPreviouslyUsedExperiments; + + /** + * @param bool[] $experiments + * @phpstan-param array $experiments + */ + public function __construct(array $experiments, bool $hasPreviouslyUsedExperiments){ + $this->experiments = $experiments; + $this->hasPreviouslyUsedExperiments = $hasPreviouslyUsedExperiments; + } + + /** @return bool[] */ + public function getExperiments() : array{ return $this->experiments; } + + public function hasPreviouslyUsedExperiments() : bool{ return $this->hasPreviouslyUsedExperiments; } + + public static function read(NetworkBinaryStream $in) : self{ + $experiments = []; + for($i = 0, $len = $in->getLInt(); $i < $len; ++$i){ + $experimentName = $in->getString(); + $enabled = $in->getBool(); + $experiments[$experimentName] = $enabled; + } + $hasPreviouslyUsedExperiments = $in->getBool(); + return new self($experiments, $hasPreviouslyUsedExperiments); + } + + public function write(NetworkBinaryStream $out) : void{ + $out->putLInt(count($this->experiments)); + foreach($this->experiments as $experimentName => $enabled){ + $out->putString($experimentName); + $out->putBool($enabled); + } + $out->putBool($this->hasPreviouslyUsedExperiments); + } +} diff --git a/src/pocketmine/network/mcpe/protocol/types/ItemComponentPacketEntry.php b/src/pocketmine/network/mcpe/protocol/types/ItemComponentPacketEntry.php new file mode 100644 index 000000000..dbe120701 --- /dev/null +++ b/src/pocketmine/network/mcpe/protocol/types/ItemComponentPacketEntry.php @@ -0,0 +1,43 @@ +name = $name; + $this->componentNbt = $componentNbt; + } + + public function getName() : string{ return $this->name; } + + public function getComponentNbt() : CompoundTag{ return $this->componentNbt; } +} diff --git a/src/pocketmine/network/mcpe/protocol/ActorFallPacket.php b/src/pocketmine/network/mcpe/protocol/types/ItemTypeEntry.php similarity index 50% rename from src/pocketmine/network/mcpe/protocol/ActorFallPacket.php rename to src/pocketmine/network/mcpe/protocol/types/ItemTypeEntry.php index 645bc14cf..79d063bc6 100644 --- a/src/pocketmine/network/mcpe/protocol/ActorFallPacket.php +++ b/src/pocketmine/network/mcpe/protocol/types/ItemTypeEntry.php @@ -21,35 +21,26 @@ declare(strict_types=1); -namespace pocketmine\network\mcpe\protocol; +namespace pocketmine\network\mcpe\protocol\types; -#include - -use pocketmine\network\mcpe\NetworkSession; - -class ActorFallPacket extends DataPacket{ - public const NETWORK_ID = ProtocolInfo::ACTOR_FALL_PACKET; +final class ItemTypeEntry{ + /** @var string */ + private $stringId; /** @var int */ - public $entityRuntimeId; - /** @var float */ - public $fallDistance; + private $numericId; /** @var bool */ - public $isInVoid; + private $componentBased; - protected function decodePayload(){ - $this->entityRuntimeId = $this->getEntityRuntimeId(); - $this->fallDistance = $this->getLFloat(); - $this->isInVoid = $this->getBool(); + public function __construct(string $stringId, int $numericId, bool $componentBased){ + $this->stringId = $stringId; + $this->numericId = $numericId; + $this->componentBased = $componentBased; } - protected function encodePayload(){ - $this->putEntityRuntimeId($this->entityRuntimeId); - $this->putLFloat($this->fallDistance); - $this->putBool($this->isInVoid); - } + public function getStringId() : string{ return $this->stringId; } - public function handle(NetworkSession $session) : bool{ - return $session->handleActorFall($this); - } + public function getNumericId() : int{ return $this->numericId; } + + public function isComponentBased() : bool{ return $this->componentBased; } } diff --git a/src/pocketmine/network/mcpe/protocol/types/PlayerMovementType.php b/src/pocketmine/network/mcpe/protocol/types/PlayerMovementType.php new file mode 100644 index 000000000..0521742ba --- /dev/null +++ b/src/pocketmine/network/mcpe/protocol/types/PlayerMovementType.php @@ -0,0 +1,31 @@ +image = $image; $this->type = $type; $this->frames = $frames; + $this->expressionType = $expressionType; } /** @@ -62,4 +68,6 @@ class SkinAnimation{ public function getFrames() : float{ return $this->frames; } + + public function getExpressionType() : int{ return $this->expressionType; } } diff --git a/src/pocketmine/network/mcpe/protocol/types/inventory/stackresponse/ItemStackResponse.php b/src/pocketmine/network/mcpe/protocol/types/inventory/stackresponse/ItemStackResponse.php index 7611776fd..184e4fea8 100644 --- a/src/pocketmine/network/mcpe/protocol/types/inventory/stackresponse/ItemStackResponse.php +++ b/src/pocketmine/network/mcpe/protocol/types/inventory/stackresponse/ItemStackResponse.php @@ -28,8 +28,13 @@ use function count; final class ItemStackResponse{ - /** @var bool */ - private $ok; + public const RESULT_OK = 0; + public const RESULT_ERROR = 1; + //TODO: there are a ton more possible result types but we don't need them yet and they are wayyyyyy too many for me + //to waste my time on right now... + + /** @var int */ + private $result; /** @var int */ private $requestId; /** @var ItemStackResponseContainerInfo[] */ @@ -38,13 +43,13 @@ final class ItemStackResponse{ /** * @param ItemStackResponseContainerInfo[] $containerInfos */ - public function __construct(bool $ok, int $requestId, array $containerInfos){ - $this->ok = $ok; + public function __construct(int $result, int $requestId, array $containerInfos){ + $this->result = $result; $this->requestId = $requestId; $this->containerInfos = $containerInfos; } - public function isOk() : bool{ return $this->ok; } + public function getResult() : int{ return $this->result; } public function getRequestId() : int{ return $this->requestId; } @@ -52,17 +57,17 @@ final class ItemStackResponse{ public function getContainerInfos() : array{ return $this->containerInfos; } public static function read(NetworkBinaryStream $in) : self{ - $ok = $in->getBool(); + $result = $in->getByte(); $requestId = $in->readGenericTypeNetworkId(); $containerInfos = []; for($i = 0, $len = $in->getUnsignedVarInt(); $i < $len; ++$i){ $containerInfos[] = ItemStackResponseContainerInfo::read($in); } - return new self($ok, $requestId, $containerInfos); + return new self($result, $requestId, $containerInfos); } public function write(NetworkBinaryStream $out) : void{ - $out->putBool($this->ok); + $out->putByte($this->result); $out->writeGenericTypeNetworkId($this->requestId); $out->putUnsignedVarInt(count($this->containerInfos)); foreach($this->containerInfos as $containerInfo){ diff --git a/src/pocketmine/resources/vanilla b/src/pocketmine/resources/vanilla index afc885ccc..14f4a765e 160000 --- a/src/pocketmine/resources/vanilla +++ b/src/pocketmine/resources/vanilla @@ -1 +1 @@ -Subproject commit afc885cccae38048d309911f2c5ddcdcb6af8152 +Subproject commit 14f4a765eba40b2c65c6dcd061cbfea6e1d3d4cc From d19db5d2e44d0925798c288247c3bddb71d23975 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 21 Nov 2020 01:16:04 +0000 Subject: [PATCH 2/6] fix phpstan warnings --- .../network/mcpe/NetworkBinaryStream.php | 5 +++- tests/phpstan/configs/l7-baseline.neon | 25 ++++--------------- 2 files changed, 9 insertions(+), 21 deletions(-) diff --git a/src/pocketmine/network/mcpe/NetworkBinaryStream.php b/src/pocketmine/network/mcpe/NetworkBinaryStream.php index fdf42b321..0fc159fc9 100644 --- a/src/pocketmine/network/mcpe/NetworkBinaryStream.php +++ b/src/pocketmine/network/mcpe/NetworkBinaryStream.php @@ -50,6 +50,7 @@ use pocketmine\network\mcpe\protocol\types\StructureEditorData; use pocketmine\network\mcpe\protocol\types\StructureSettings; use pocketmine\utils\BinaryStream; use pocketmine\utils\UUID; +use function assert; use function count; use function strlen; @@ -765,7 +766,9 @@ class NetworkBinaryStream extends BinaryStream{ public function getNbtRoot() : NamedTag{ $offset = $this->getOffset(); try{ - return (new NetworkLittleEndianNBTStream())->read($this->getBuffer(), false, $offset, 512); + $result = (new NetworkLittleEndianNBTStream())->read($this->getBuffer(), false, $offset, 512); + assert($result instanceof NamedTag, "doMultiple is false so we should definitely have a NamedTag here"); + return $result; }finally{ $this->setOffset($offset); } diff --git a/tests/phpstan/configs/l7-baseline.neon b/tests/phpstan/configs/l7-baseline.neon index d5e05fdb5..0abcf76ad 100644 --- a/tests/phpstan/configs/l7-baseline.neon +++ b/tests/phpstan/configs/l7-baseline.neon @@ -825,11 +825,6 @@ parameters: count: 1 path: ../../../src/pocketmine/network/mcpe/VerifyLoginTask.php - - - message: "#^Parameter \\#1 \\$buffer of method pocketmine\\\\nbt\\\\NBTStream\\:\\:read\\(\\) expects string, string\\|false given\\.$#" - count: 1 - path: ../../../src/pocketmine/network/mcpe/convert/RuntimeBlockMapping.php - - message: "#^Parameter \\#1 \\$json of function json_decode expects string, string\\|false given\\.$#" count: 1 @@ -865,6 +860,11 @@ parameters: count: 1 path: ../../../src/pocketmine/network/mcpe/protocol/BiomeDefinitionListPacket.php + - + message: "#^Parameter \\#1 \\$str of method pocketmine\\\\utils\\\\BinaryStream\\:\\:put\\(\\) expects string, string\\|false given\\.$#" + count: 1 + path: ../../../src/pocketmine/network/mcpe/protocol/ItemComponentPacket.php + - message: "#^Property pocketmine\\\\network\\\\mcpe\\\\protocol\\\\LevelEventGenericPacket\\:\\:\\$eventData \\(string\\) does not accept string\\|false\\.$#" count: 1 @@ -875,26 +875,11 @@ parameters: count: 1 path: ../../../src/pocketmine/network/mcpe/protocol/PositionTrackingDBServerBroadcastPacket.php - - - message: "#^Static property pocketmine\\\\network\\\\mcpe\\\\protocol\\\\StartGamePacket\\:\\:\\$blockTableCache \\(string\\|null\\) does not accept string\\|false\\.$#" - count: 1 - path: ../../../src/pocketmine/network/mcpe/protocol/StartGamePacket.php - - message: "#^Parameter \\#1 \\$str of method pocketmine\\\\utils\\\\BinaryStream\\:\\:put\\(\\) expects string, string\\|false given\\.$#" - count: 2 - path: ../../../src/pocketmine/network/mcpe/protocol/StartGamePacket.php - - - - message: "#^Parameter \\#1 \\$json of function json_decode expects string, string\\|false given\\.$#" count: 1 path: ../../../src/pocketmine/network/mcpe/protocol/StartGamePacket.php - - - message: "#^Property pocketmine\\\\network\\\\mcpe\\\\protocol\\\\UpdateBlockPropertiesPacket\\:\\:\\$nbt \\(string\\) does not accept string\\|false\\.$#" - count: 1 - path: ../../../src/pocketmine/network/mcpe/protocol/UpdateBlockPropertiesPacket.php - - message: "#^Parameter \\#2 \\$resourcePatch of class pocketmine\\\\network\\\\mcpe\\\\protocol\\\\types\\\\SkinData constructor expects string, string\\|false given\\.$#" count: 1 From d2f68836c60fd9db1bab3e8e70bc817dda6664ea Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 21 Nov 2020 01:25:06 +0000 Subject: [PATCH 3/6] Release 3.16.0 --- changelogs/3.16.md | 18 ++++++++++++++++++ src/pocketmine/VersionInfo.php | 4 ++-- 2 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 changelogs/3.16.md diff --git a/changelogs/3.16.md b/changelogs/3.16.md new file mode 100644 index 000000000..717f07acb --- /dev/null +++ b/changelogs/3.16.md @@ -0,0 +1,18 @@ +**For Minecraft: Bedrock Edition 1.16.100** + +### 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.16.0 +- Added support for Minecraft: Bedrock Edition 1.16.100. +- Removed compatibility with earlier versions. +- Added new custom composer commands `make-server` and `make-devtools` to ease setting up a development environment and building the server. + +## 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 diff --git a/src/pocketmine/VersionInfo.php b/src/pocketmine/VersionInfo.php index 7626b0092..fe8105c08 100644 --- a/src/pocketmine/VersionInfo.php +++ b/src/pocketmine/VersionInfo.php @@ -33,6 +33,6 @@ if(defined('pocketmine\_VERSION_INFO_INCLUDED')){ const _VERSION_INFO_INCLUDED = true; const NAME = "PocketMine-MP"; -const BASE_VERSION = "3.15.5"; -const IS_DEVELOPMENT_BUILD = true; +const BASE_VERSION = "3.16.0"; +const IS_DEVELOPMENT_BUILD = false; const BUILD_NUMBER = 0; From 23849b7f630de818260a439ea3807b826cdb4e0c Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 21 Nov 2020 01:25:06 +0000 Subject: [PATCH 4/6] 3.16.1 is next --- src/pocketmine/VersionInfo.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pocketmine/VersionInfo.php b/src/pocketmine/VersionInfo.php index fe8105c08..c8a2da70f 100644 --- a/src/pocketmine/VersionInfo.php +++ b/src/pocketmine/VersionInfo.php @@ -33,6 +33,6 @@ if(defined('pocketmine\_VERSION_INFO_INCLUDED')){ const _VERSION_INFO_INCLUDED = true; const NAME = "PocketMine-MP"; -const BASE_VERSION = "3.16.0"; -const IS_DEVELOPMENT_BUILD = false; +const BASE_VERSION = "3.16.1"; +const IS_DEVELOPMENT_BUILD = true; const BUILD_NUMBER = 0; From e8e6b9304c4abe6f7e588b26594f189f0cd126fd Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 21 Nov 2020 17:46:32 +0000 Subject: [PATCH 5/6] phpstan 0.12.57 --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 04ce4c5d5..3f78197fd 100644 --- a/composer.json +++ b/composer.json @@ -38,7 +38,7 @@ "composer-runtime-api": "^2.0" }, "require-dev": { - "phpstan/phpstan": "0.12.54", + "phpstan/phpstan": "0.12.57", "phpstan/phpstan-phpunit": "^0.12.6", "phpstan/phpstan-strict-rules": "^0.12.2", "phpunit/phpunit": "^9.2" diff --git a/composer.lock b/composer.lock index 314030e20..5ab343499 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f7b6b54d43e00372b7dea787d863d8b9", + "content-hash": "da01dc4097df12e4b124207d071b81f4", "packages": [ { "name": "adhocore/json-comment", @@ -996,16 +996,16 @@ }, { "name": "phpstan/phpstan", - "version": "0.12.54", + "version": "0.12.57", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "45c7b999a4b7dd9ac5558bdaaf23dcebbef88223" + "reference": "f9909d1d0c44b4cbaf72babcf80e8f14d6fdd55b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/45c7b999a4b7dd9ac5558bdaaf23dcebbef88223", - "reference": "45c7b999a4b7dd9ac5558bdaaf23dcebbef88223", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/f9909d1d0c44b4cbaf72babcf80e8f14d6fdd55b", + "reference": "f9909d1d0c44b4cbaf72babcf80e8f14d6fdd55b", "shasum": "" }, "require": { @@ -1036,7 +1036,7 @@ "description": "PHPStan - PHP Static Analysis Tool", "support": { "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/0.12.54" + "source": "https://github.com/phpstan/phpstan/tree/0.12.57" }, "funding": [ { @@ -1052,7 +1052,7 @@ "type": "tidelift" } ], - "time": "2020-11-05T13:36:26+00:00" + "time": "2020-11-21T12:53:28+00:00" }, { "name": "phpstan/phpstan-phpunit", From 9e85ee4a7a9ce88a02c930baa940980fde583edc Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 21 Nov 2020 18:01:56 +0000 Subject: [PATCH 6/6] Fixed missing field on Persona skin encode --- src/pocketmine/network/mcpe/NetworkBinaryStream.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pocketmine/network/mcpe/NetworkBinaryStream.php b/src/pocketmine/network/mcpe/NetworkBinaryStream.php index 0fc159fc9..79ae169f8 100644 --- a/src/pocketmine/network/mcpe/NetworkBinaryStream.php +++ b/src/pocketmine/network/mcpe/NetworkBinaryStream.php @@ -148,6 +148,7 @@ class NetworkBinaryStream extends BinaryStream{ $this->putSkinImage($animation->getImage()); $this->putLInt($animation->getType()); $this->putLFloat($animation->getFrames()); + $this->putLInt($animation->getExpressionType()); } $this->putSkinImage($skin->getCapeImage()); $this->putString($skin->getGeometryData());