diff --git a/src/pocketmine/Player.php b/src/pocketmine/Player.php index 1ba67ecb2..52dcb3994 100644 --- a/src/pocketmine/Player.php +++ b/src/pocketmine/Player.php @@ -146,6 +146,10 @@ 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\PlayerPermissions; +use pocketmine\network\mcpe\protocol\types\SkinAdapterSingleton; +use pocketmine\network\mcpe\protocol\types\SkinAnimation; +use pocketmine\network\mcpe\protocol\types\SkinData; +use pocketmine\network\mcpe\protocol\types\SkinImage; use pocketmine\network\mcpe\protocol\UpdateAttributesPacket; use pocketmine\network\mcpe\protocol\UpdateBlockPacket; use pocketmine\network\mcpe\VerifyLoginTask; @@ -1112,9 +1116,11 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{ } } - protected function sendRespawnPacket(Vector3 $pos){ + protected function sendRespawnPacket(Vector3 $pos, int $respawnState = RespawnPacket::SEARCHING_FOR_SPAWN){ $pk = new RespawnPacket(); $pk->position = $pos->add(0, $this->baseOffset, 0); + $pk->respawnState = $respawnState; + $pk->entityRuntimeId = $this->getId(); $this->dataPacket($pk); } @@ -1913,14 +1919,27 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{ $this->uuid = UUID::fromString($packet->clientUUID); $this->rawUUID = $this->uuid->toBinary(); - $skin = new Skin( + $animations = []; + foreach($packet->clientData["AnimatedImageData"] as $animation){ + $animations[] = new SkinAnimation(new SkinImage($animation["ImageHeight"], $animation["ImageWidth"], $animation["Image"]), $animation["Type"], $animation["Frames"]); + } + + $skinData = new SkinData( $packet->clientData["SkinId"], - base64_decode($packet->clientData["SkinData"] ?? ""), - base64_decode($packet->clientData["CapeData"] ?? ""), - $packet->clientData["SkinGeometryName"] ?? "", - base64_decode($packet->clientData["SkinGeometry"] ?? "") + base64_decode($packet->clientData["SkinResourcePatch"] ?? ""), + new SkinImage($packet->clientData["SkinImageHeight"], $packet->clientData["SkinImageWidth"], base64_decode($packet->clientData["SkinData"])), + $animations, + new SkinImage($packet->clientData["CapeImageHeight"], $packet->clientData["CapeImageWidth"], base64_decode($packet->clientData["CapeData"] ?? "")), + base64_decode($packet->clientData["SkinGeometryData"] ?? ""), + base64_decode($packet->clientData["AnimationData"] ?? ""), + $packet->clientData["PremiumSkin"] ?? false, + $packet->clientData["PersonaSkin"] ?? false, + $packet->clientData["CapeOnClassicSkin"] ?? false, + $packet->clientData["CapeId"] ?? "" ); + $skin = SkinAdapterSingleton::get()->fromSkinData($skinData); + if(!$skin->isValid()){ $this->close("", "disconnectionScreen.invalidSkin"); @@ -2156,7 +2175,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{ $pk->spawnZ = $spawnPosition->getFloorZ(); $pk->hasAchievementsDisabled = true; $pk->time = $this->level->getTime(); - $pk->eduMode = false; + $pk->eduEditionOffer = 0; $pk->rainLevel = 0; //TODO: implement these properly $pk->lightningLevel = 0; $pk->commandsEnabled = true; @@ -2491,6 +2510,27 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{ return true; case InventoryTransactionPacket::USE_ITEM_ACTION_CLICK_AIR: + if($this->isUsingItem()){ + $slot = $this->inventory->getItemInHand(); + if($slot instanceof Consumable){ + $ev = new PlayerItemConsumeEvent($this, $slot); + if($this->hasItemCooldown($slot)){ + $ev->setCancelled(); + } + $ev->call(); + if($ev->isCancelled() or !$this->consumeObject($slot)){ + $this->inventory->sendContents($this); + return true; + } + $this->resetItemCooldown($slot); + if($this->isSurvival()){ + $slot->pop(); + $this->inventory->setItemInHand($slot); + $this->inventory->addItem($slot->getResidue()); + } + $this->setUsingItem(false); + } + } $directionVector = $this->getDirectionVector(); if($this->isCreative()){ @@ -2644,33 +2684,6 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{ } return true; - case InventoryTransactionPacket::RELEASE_ITEM_ACTION_CONSUME: - $slot = $this->inventory->getItemInHand(); - - if($slot instanceof Consumable){ - $ev = new PlayerItemConsumeEvent($this, $slot); - if($this->hasItemCooldown($slot)){ - $ev->setCancelled(); - } - $ev->call(); - - if($ev->isCancelled() or !$this->consumeObject($slot)){ - $this->inventory->sendContents($this); - return true; - } - - $this->resetItemCooldown($slot); - - if($this->isSurvival()){ - $slot->pop(); - $this->inventory->setItemInHand($slot); - $this->inventory->addItem($slot->getResidue()); - } - - return true; - } - - break; default: break; } @@ -2913,6 +2926,15 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{ return true; } + public function handleRespawn(RespawnPacket $packet) : bool{ + if(!$this->isAlive() && $packet->respawnState === RespawnPacket::CLIENT_READY_TO_SPAWN){ + $this->sendRespawnPacket($this, RespawnPacket::READY_TO_SPAWN); + return true; + } + + return false; + } + /** * Drops an item on the ground in front of the player. Returns if the item drop was successful. * @@ -3805,7 +3827,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{ $this->addWindow($this->getArmorInventory(), ContainerIds::ARMOR, true); $this->cursorInventory = new PlayerCursorInventory($this); - $this->addWindow($this->cursorInventory, ContainerIds::CURSOR, true); + $this->addWindow($this->cursorInventory, ContainerIds::UI, true); $this->craftingGrid = new CraftingGrid($this, CraftingGrid::SIZE_SMALL); diff --git a/src/pocketmine/Server.php b/src/pocketmine/Server.php index be10c0d90..bcc068d7a 100644 --- a/src/pocketmine/Server.php +++ b/src/pocketmine/Server.php @@ -76,6 +76,7 @@ use pocketmine\network\mcpe\protocol\DataPacket; use pocketmine\network\mcpe\protocol\PlayerListPacket; use pocketmine\network\mcpe\protocol\ProtocolInfo; use pocketmine\network\mcpe\protocol\types\PlayerListEntry; +use pocketmine\network\mcpe\protocol\types\SkinAdapterSingleton; use pocketmine\network\mcpe\RakLibInterface; use pocketmine\network\Network; use pocketmine\network\query\QueryHandler; @@ -2395,7 +2396,7 @@ class Server{ $pk = new PlayerListPacket(); $pk->type = PlayerListPacket::TYPE_ADD; - $pk->entries[] = PlayerListEntry::createAdditionEntry($uuid, $entityId, $name, $skin, $xboxUserId); + $pk->entries[] = PlayerListEntry::createAdditionEntry($uuid, $entityId, $name, SkinAdapterSingleton::get()->toSkinData($skin), $xboxUserId); $this->broadcastPacket($players ?? $this->playerList, $pk); } @@ -2418,7 +2419,7 @@ class Server{ $pk = new PlayerListPacket(); $pk->type = PlayerListPacket::TYPE_ADD; foreach($this->playerList as $player){ - $pk->entries[] = PlayerListEntry::createAdditionEntry($player->getUniqueId(), $player->getId(), $player->getDisplayName(), $player->getSkin(), $player->getXuid()); + $pk->entries[] = PlayerListEntry::createAdditionEntry($player->getUniqueId(), $player->getId(), $player->getDisplayName(), SkinAdapterSingleton::get()->toSkinData($player->getSkin()), $player->getXuid()); } $p->dataPacket($pk); diff --git a/src/pocketmine/entity/Human.php b/src/pocketmine/entity/Human.php index 0a9def938..e0f2c9abc 100644 --- a/src/pocketmine/entity/Human.php +++ b/src/pocketmine/entity/Human.php @@ -53,6 +53,7 @@ use pocketmine\network\mcpe\protocol\LevelSoundEventPacket; use pocketmine\network\mcpe\protocol\PlayerListPacket; use pocketmine\network\mcpe\protocol\PlayerSkinPacket; use pocketmine\network\mcpe\protocol\types\PlayerListEntry; +use pocketmine\network\mcpe\protocol\types\SkinAdapterSingleton; use pocketmine\Player; use pocketmine\utils\UUID; use function array_filter; @@ -182,7 +183,7 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{ public function sendSkin(?array $targets = null) : void{ $pk = new PlayerSkinPacket(); $pk->uuid = $this->getUniqueId(); - $pk->skin = $this->skin; + $pk->skin = SkinAdapterSingleton::get()->toSkinData($this->skin); $this->server->broadcastPacket($targets ?? $this->hasSpawned, $pk); } @@ -850,7 +851,7 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{ /* we don't use Server->updatePlayerListData() because that uses batches, which could cause race conditions in async compression mode */ $pk = new PlayerListPacket(); $pk->type = PlayerListPacket::TYPE_ADD; - $pk->entries = [PlayerListEntry::createAdditionEntry($this->uuid, $this->id, $this->getName(), $this->skin)]; + $pk->entries = [PlayerListEntry::createAdditionEntry($this->uuid, $this->id, $this->getName(), SkinAdapterSingleton::get()->toSkinData($this->skin))]; $player->dataPacket($pk); } diff --git a/src/pocketmine/level/Explosion.php b/src/pocketmine/level/Explosion.php index e234fdd3e..d3906790e 100644 --- a/src/pocketmine/level/Explosion.php +++ b/src/pocketmine/level/Explosion.php @@ -38,7 +38,6 @@ use pocketmine\level\particle\HugeExplodeSeedParticle; use pocketmine\level\utils\SubChunkIteratorManager; use pocketmine\math\AxisAlignedBB; use pocketmine\math\Vector3; -use pocketmine\network\mcpe\protocol\ExplodePacket; use pocketmine\network\mcpe\protocol\LevelSoundEventPacket; use pocketmine\tile\Chest; use pocketmine\tile\Container; @@ -260,12 +259,6 @@ class Explosion{ $send[] = new Vector3($block->x - $source->x, $block->y - $source->y, $block->z - $source->z); } - $pk = new ExplodePacket(); - $pk->position = $this->source->asVector3(); - $pk->radius = $this->size; - $pk->records = $send; - $this->level->broadcastPacketToViewers($source, $pk); - $this->level->addParticle(new HugeExplodeSeedParticle($source)); $this->level->broadcastLevelSoundEvent($source, LevelSoundEventPacket::SOUND_EXPLODE); diff --git a/src/pocketmine/level/particle/FloatingTextParticle.php b/src/pocketmine/level/particle/FloatingTextParticle.php index d12282874..29ef05133 100644 --- a/src/pocketmine/level/particle/FloatingTextParticle.php +++ b/src/pocketmine/level/particle/FloatingTextParticle.php @@ -32,6 +32,7 @@ use pocketmine\network\mcpe\protocol\AddPlayerPacket; use pocketmine\network\mcpe\protocol\PlayerListPacket; use pocketmine\network\mcpe\protocol\RemoveActorPacket; use pocketmine\network\mcpe\protocol\types\PlayerListEntry; +use pocketmine\network\mcpe\protocol\types\SkinAdapterSingleton; use pocketmine\utils\UUID; use function str_repeat; @@ -96,7 +97,9 @@ class FloatingTextParticle extends Particle{ $add = new PlayerListPacket(); $add->type = PlayerListPacket::TYPE_ADD; - $add->entries = [PlayerListEntry::createAdditionEntry($uuid, $this->entityId, $name, new Skin("Standard_Custom", str_repeat("\x00", 8192)))]; + $add->entries = [PlayerListEntry::createAdditionEntry($uuid, $this->entityId, $name, SkinAdapterSingleton::get()->toSkinData(new Skin( + "Standard_Custom", str_repeat("\x00", 8192), "", "geometry.humanoid.custom" + )))]; $p[] = $add; $pk = new AddPlayerPacket(); diff --git a/src/pocketmine/network/mcpe/NetworkBinaryStream.php b/src/pocketmine/network/mcpe/NetworkBinaryStream.php index 85c6766ab..0b6b8a097 100644 --- a/src/pocketmine/network/mcpe/NetworkBinaryStream.php +++ b/src/pocketmine/network/mcpe/NetworkBinaryStream.php @@ -27,6 +27,7 @@ namespace pocketmine\network\mcpe; use pocketmine\entity\Attribute; use pocketmine\entity\Entity; +use pocketmine\entity\Skin; use pocketmine\item\Durable; use pocketmine\item\Item; use pocketmine\item\ItemFactory; @@ -37,6 +38,9 @@ use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\IntTag; use pocketmine\network\mcpe\protocol\types\CommandOriginData; use pocketmine\network\mcpe\protocol\types\EntityLink; +use pocketmine\network\mcpe\protocol\types\SkinData; +use pocketmine\network\mcpe\protocol\types\SkinImage; +use pocketmine\network\mcpe\protocol\types\SkinAnimation; use pocketmine\network\mcpe\protocol\types\StructureSettings; use pocketmine\utils\BinaryStream; use pocketmine\utils\UUID; @@ -74,6 +78,66 @@ class NetworkBinaryStream extends BinaryStream{ $this->putLInt($uuid->getPart(2)); } + public function getSkin() : SkinData{ + $skinId = $this->getString(); + $skinResourcePatch = $this->getString(); + $skinData = $this->getSkinImage(); + $animationCount = $this->getLInt(); + $animations = []; + for($i = 0; $i < $animationCount; ++$i){ + $animations[] = new SkinAnimation( + $skinImage = $this->getSkinImage(), + $animationType = $this->getLInt(), + $animationFrames = $this->getLFloat() + ); + } + $capeData = $this->getSkinImage(); + $geometryData = $this->getString(); + $animationData = $this->getString(); + $premium = $this->getBool(); + $persona = $this->getBool(); + $capeOnClassic = $this->getBool(); + $capeId = $this->getString(); + $fullSkinId = $this->getString(); + + return new SkinData($skinId, $skinResourcePatch, $skinData, $animations, $capeData, $geometryData, $animationData, $premium, $persona, $capeOnClassic, $capeId); + } + + public function putSkin(SkinData $skin){ + $this->putString($skin->getSkinId()); + $this->putString($skin->getResourcePatch()); + $this->putSkinImage($skin->getSkinImage()); + $this->putLInt(count($skin->getAnimations())); + foreach($skin->getAnimations() as $animation){ + $this->putSkinImage($animation->getImage()); + $this->putLInt($animation->getType()); + $this->putLFloat($animation->getFrames()); + } + $this->putSkinImage($skin->getCapeImage()); + $this->putString($skin->getGeometryData()); + $this->putString($skin->getAnimationData()); + $this->putBool($skin->isPremium()); + $this->putBool($skin->isPersona()); + $this->putBool($skin->isPersonaCapeOnClassic()); + $this->putString($skin->getCapeId()); + + //this has to be unique or the client will do stupid things + $this->putString(UUID::fromRandom()->toString()); //full skin ID + } + + private function getSkinImage() : SkinImage{ + $width = $this->getLInt(); + $height = $this->getLInt(); + $data = $this->getString(); + return new SkinImage($height, $width, $data); + } + + private function putSkinImage(SkinImage $image) : void{ + $this->putLInt($image->getWidth()); + $this->putLInt($image->getHeight()); + $this->putString($image->getData()); + } + public function getSlot() : Item{ $id = $this->getVarInt(); if($id === 0){ diff --git a/src/pocketmine/network/mcpe/NetworkSession.php b/src/pocketmine/network/mcpe/NetworkSession.php index 4de360f3a..19a5a14ba 100644 --- a/src/pocketmine/network/mcpe/NetworkSession.php +++ b/src/pocketmine/network/mcpe/NetworkSession.php @@ -34,6 +34,7 @@ use pocketmine\network\mcpe\protocol\AddPaintingPacket; use pocketmine\network\mcpe\protocol\AddPlayerPacket; use pocketmine\network\mcpe\protocol\AdventureSettingsPacket; use pocketmine\network\mcpe\protocol\AnimatePacket; +use pocketmine\network\mcpe\protocol\AnvilDamagePacket; use pocketmine\network\mcpe\protocol\AutomationClientConnectPacket; use pocketmine\network\mcpe\protocol\AvailableActorIdentifiersPacket; use pocketmine\network\mcpe\protocol\AvailableCommandsPacket; @@ -54,6 +55,7 @@ use pocketmine\network\mcpe\protocol\ClientToServerHandshakePacket; use pocketmine\network\mcpe\protocol\CommandBlockUpdatePacket; use pocketmine\network\mcpe\protocol\CommandOutputPacket; use pocketmine\network\mcpe\protocol\CommandRequestPacket; +use pocketmine\network\mcpe\protocol\CompletedUsingItemPacket; use pocketmine\network\mcpe\protocol\ContainerClosePacket; use pocketmine\network\mcpe\protocol\ContainerOpenPacket; use pocketmine\network\mcpe\protocol\ContainerSetDataPacket; @@ -61,8 +63,9 @@ use pocketmine\network\mcpe\protocol\CraftingDataPacket; use pocketmine\network\mcpe\protocol\CraftingEventPacket; use pocketmine\network\mcpe\protocol\DataPacket; use pocketmine\network\mcpe\protocol\DisconnectPacket; +use pocketmine\network\mcpe\protocol\EducationSettingsPacket; +use pocketmine\network\mcpe\protocol\EmotePacket; use pocketmine\network\mcpe\protocol\EventPacket; -use pocketmine\network\mcpe\protocol\ExplodePacket; use pocketmine\network\mcpe\protocol\GameRulesChangedPacket; use pocketmine\network\mcpe\protocol\GuiDataPickItemPacket; use pocketmine\network\mcpe\protocol\HurtArmorPacket; @@ -90,12 +93,15 @@ use pocketmine\network\mcpe\protocol\ModalFormResponsePacket; use pocketmine\network\mcpe\protocol\MoveActorAbsolutePacket; use pocketmine\network\mcpe\protocol\MoveActorDeltaPacket; use pocketmine\network\mcpe\protocol\MovePlayerPacket; +use pocketmine\network\mcpe\protocol\MultiplayerSettingsPacket; use pocketmine\network\mcpe\protocol\NetworkChunkPublisherUpdatePacket; +use pocketmine\network\mcpe\protocol\NetworkSettingsPacket; use pocketmine\network\mcpe\protocol\NetworkStackLatencyPacket; use pocketmine\network\mcpe\protocol\NpcRequestPacket; use pocketmine\network\mcpe\protocol\OnScreenTextureAnimationPacket; use pocketmine\network\mcpe\protocol\PhotoTransferPacket; use pocketmine\network\mcpe\protocol\PlayerActionPacket; +use pocketmine\network\mcpe\protocol\PlayerAuthInputPacket; use pocketmine\network\mcpe\protocol\PlayerHotbarPacket; use pocketmine\network\mcpe\protocol\PlayerInputPacket; use pocketmine\network\mcpe\protocol\PlayerListPacket; @@ -134,6 +140,7 @@ use pocketmine\network\mcpe\protocol\SetScoreboardIdentityPacket; use pocketmine\network\mcpe\protocol\SetScorePacket; use pocketmine\network\mcpe\protocol\SetSpawnPositionPacket; use pocketmine\network\mcpe\protocol\SetTimePacket; +use pocketmine\network\mcpe\protocol\SettingsCommandPacket; use pocketmine\network\mcpe\protocol\SetTitlePacket; use pocketmine\network\mcpe\protocol\ShowCreditsPacket; use pocketmine\network\mcpe\protocol\ShowProfilePacket; @@ -144,11 +151,12 @@ use pocketmine\network\mcpe\protocol\SpawnParticleEffectPacket; use pocketmine\network\mcpe\protocol\StartGamePacket; use pocketmine\network\mcpe\protocol\StopSoundPacket; use pocketmine\network\mcpe\protocol\StructureBlockUpdatePacket; -use pocketmine\network\mcpe\protocol\StructureTemplateDataExportRequestPacket; -use pocketmine\network\mcpe\protocol\StructureTemplateDataExportResponsePacket; +use pocketmine\network\mcpe\protocol\StructureTemplateDataRequestPacket; +use pocketmine\network\mcpe\protocol\StructureTemplateDataResponsePacket; use pocketmine\network\mcpe\protocol\SubClientLoginPacket; use pocketmine\network\mcpe\protocol\TakeItemActorPacket; use pocketmine\network\mcpe\protocol\TextPacket; +use pocketmine\network\mcpe\protocol\TickSyncPacket; use pocketmine\network\mcpe\protocol\TransferPacket; use pocketmine\network\mcpe\protocol\UpdateAttributesPacket; use pocketmine\network\mcpe\protocol\UpdateBlockPacket; @@ -247,7 +255,7 @@ abstract class NetworkSession{ return false; } - public function handleExplode(ExplodePacket $packet) : bool{ + public function handleTickSync(TickSyncPacket $packet) : bool{ return false; } @@ -679,11 +687,11 @@ abstract class NetworkSession{ return false; } - public function handleStructureTemplateDataExportRequest(StructureTemplateDataExportRequestPacket $packet) : bool{ + public function handleStructureTemplateDataRequest(StructureTemplateDataRequestPacket $packet) : bool{ return false; } - public function handleStructureTemplateDataExportResponse(StructureTemplateDataExportResponsePacket $packet) : bool{ + public function handleStructureTemplateDataResponse(StructureTemplateDataResponsePacket $packet) : bool{ return false; } @@ -698,4 +706,36 @@ abstract class NetworkSession{ public function handleClientCacheMissResponse(ClientCacheMissResponsePacket $packet) : bool{ return false; } + + public function handleEducationSettings(EducationSettingsPacket $packet) : bool{ + return false; + } + + public function handleEmote(EmotePacket $packet) : bool{ + return false; + } + + public function handleMultiplayerSettings(MultiplayerSettingsPacket $packet) : bool{ + return false; + } + + public function handleSettingsCommand(SettingsCommandPacket $packet) : bool{ + return false; + } + + public function handleAnvilDamage(AnvilDamagePacket $packet) : bool{ + return false; + } + + public function handleCompletedUsingItem(CompletedUsingItemPacket $packet) : bool{ + return false; + } + + public function handleNetworkSettings(NetworkSettingsPacket $packet) : bool{ + return false; + } + + public function handlePlayerAuthInput(PlayerAuthInputPacket $packet) : bool{ + return false; + } } diff --git a/src/pocketmine/network/mcpe/PlayerNetworkSessionAdapter.php b/src/pocketmine/network/mcpe/PlayerNetworkSessionAdapter.php index 7e546647c..b064ae803 100644 --- a/src/pocketmine/network/mcpe/PlayerNetworkSessionAdapter.php +++ b/src/pocketmine/network/mcpe/PlayerNetworkSessionAdapter.php @@ -59,12 +59,15 @@ use pocketmine\network\mcpe\protocol\PlayerSkinPacket; use pocketmine\network\mcpe\protocol\RequestChunkRadiusPacket; use pocketmine\network\mcpe\protocol\ResourcePackChunkRequestPacket; use pocketmine\network\mcpe\protocol\ResourcePackClientResponsePacket; +use pocketmine\network\mcpe\protocol\RespawnPacket; use pocketmine\network\mcpe\protocol\ServerSettingsRequestPacket; use pocketmine\network\mcpe\protocol\SetLocalPlayerAsInitializedPacket; use pocketmine\network\mcpe\protocol\SetPlayerGameTypePacket; use pocketmine\network\mcpe\protocol\ShowCreditsPacket; use pocketmine\network\mcpe\protocol\SpawnExperienceOrbPacket; use pocketmine\network\mcpe\protocol\TextPacket; +use pocketmine\network\mcpe\protocol\types\SkinAdapterSingleton; +use pocketmine\network\mcpe\protocol\types\SkinData; use pocketmine\Player; use pocketmine\Server; use pocketmine\timings\Timings; @@ -181,6 +184,10 @@ class PlayerNetworkSessionAdapter extends NetworkSession{ return $this->player->handleAnimate($packet); } + public function handleRespawn(RespawnPacket $packet) : bool{ + return $this->player->handleRespawn($packet); + } + public function handleContainerClose(ContainerClosePacket $packet) : bool{ return $this->player->handleContainerClose($packet); } @@ -248,7 +255,7 @@ class PlayerNetworkSessionAdapter extends NetworkSession{ } public function handlePlayerSkin(PlayerSkinPacket $packet) : bool{ - return $this->player->changeSkin($packet->skin, $packet->newSkinName, $packet->oldSkinName); + return $this->player->changeSkin(SkinAdapterSingleton::get()->fromSkinData($packet->skin), $packet->newSkinName, $packet->oldSkinName); } public function handleBookEdit(BookEditPacket $packet) : bool{ diff --git a/src/pocketmine/network/mcpe/protocol/AddPlayerPacket.php b/src/pocketmine/network/mcpe/protocol/AddPlayerPacket.php index 7964b1b45..28c61b970 100644 --- a/src/pocketmine/network/mcpe/protocol/AddPlayerPacket.php +++ b/src/pocketmine/network/mcpe/protocol/AddPlayerPacket.php @@ -74,6 +74,8 @@ class AddPlayerPacket extends DataPacket{ /** @var string */ public $deviceId = ""; //TODO: fill player's device ID (???) + /** @var int */ + public $buildPlatform = -1; protected function decodePayload(){ $this->uuid = $this->getUUID(); @@ -103,6 +105,7 @@ class AddPlayerPacket extends DataPacket{ } $this->deviceId = $this->getString(); + $this->buildPlatform = $this->getLInt(); } protected function encodePayload(){ @@ -133,6 +136,7 @@ class AddPlayerPacket extends DataPacket{ } $this->putString($this->deviceId); + $this->putLInt($this->buildPlatform); } public function handle(NetworkSession $session) : bool{ diff --git a/src/pocketmine/network/mcpe/protocol/AnvilDamagePacket.php b/src/pocketmine/network/mcpe/protocol/AnvilDamagePacket.php new file mode 100644 index 000000000..f43c596e4 --- /dev/null +++ b/src/pocketmine/network/mcpe/protocol/AnvilDamagePacket.php @@ -0,0 +1,78 @@ + + +use pocketmine\network\mcpe\NetworkSession; + +class AnvilDamagePacket extends DataPacket/* implements ServerboundPacket*/{ + public const NETWORK_ID = ProtocolInfo::ANVIL_DAMAGE_PACKET; + + /** @var int */ + private $x; + /** @var int */ + private $y; + /** @var int */ + private $z; + /** @var int */ + private $damageAmount; + + public static function create(int $x, int $y, int $z, int $damageAmount) : self{ + $result = new self; + [$result->x, $result->y, $result->z] = [$x, $y, $z]; + $result->damageAmount = $damageAmount; + return $result; + } + + public function getDamageAmount() : int{ + return $this->damageAmount; + } + + public function getX() : int{ + return $this->x; + } + + public function getY() : int{ + return $this->y; + } + + public function getZ() : int{ + return $this->z; + } + + protected function decodePayload() : void{ + $this->damageAmount = $this->getByte(); + $this->getBlockPosition($this->x, $this->y, $this->z); + } + + protected function encodePayload() : void{ + $this->putByte($this->damageAmount); + $this->putBlockPosition($this->x, $this->y, $this->z); + } + + public function handle(NetworkSession $handler) : bool{ + return $handler->handleAnvilDamage($this); + } +} diff --git a/src/pocketmine/network/mcpe/protocol/AvailableActorIdentifiersPacket.php b/src/pocketmine/network/mcpe/protocol/AvailableActorIdentifiersPacket.php index c70ece1b0..e1c3baea7 100644 --- a/src/pocketmine/network/mcpe/protocol/AvailableActorIdentifiersPacket.php +++ b/src/pocketmine/network/mcpe/protocol/AvailableActorIdentifiersPacket.php @@ -27,15 +27,14 @@ namespace pocketmine\network\mcpe\protocol; use pocketmine\network\mcpe\NetworkSession; use function base64_decode; +use function file_get_contents; class AvailableActorIdentifiersPacket extends DataPacket{ public const NETWORK_ID = ProtocolInfo::AVAILABLE_ACTOR_IDENTIFIERS_PACKET; - /** - * Hardcoded NBT blob extracted from MCPE vanilla server. - * TODO: this needs to be generated dynamically, but this is here for stable backwards compatibility, so we don't care for now. - */ - private const HARDCODED_NBT_BLOB = "CgAJBmlkbGlzdArOAQgDYmlkCm1pbmVjcmFmdDoBDGV4cGVyaW1lbnRhbAABC2hhc3NwYXduZWdnAAgCaWQNbWluZWNyYWZ0Om5wYwMDcmlkhgQBCnN1bW1vbmFibGUAAAgDYmlkAAEMZXhwZXJpbWVudGFsAAELaGFzc3Bhd25lZ2cBCAJpZA1taW5lY3JhZnQ6Y293AwNyaWQWAQpzdW1tb25hYmxlAQAIA2JpZAABDGV4cGVyaW1lbnRhbAABC2hhc3NwYXduZWdnAQgCaWQabWluZWNyYWZ0OndhbmRlcmluZ190cmFkZXIDA3JpZOwBAQpzdW1tb25hYmxlAQAIA2JpZAABDGV4cGVyaW1lbnRhbAABC2hhc3NwYXduZWdnAAgCaWQRbWluZWNyYWZ0OmJhbGxvb24DA3JpZNYBAQpzdW1tb25hYmxlAAAIA2JpZAABDGV4cGVyaW1lbnRhbAABC2hhc3NwYXduZWdnAAgCaWQSbWluZWNyYWZ0OmljZV9ib21iAwNyaWTUAQEKc3VtbW9uYWJsZQAACANiaWQAAQxleHBlcmltZW50YWwAAQtoYXNzcGF3bmVnZwEIAmlkDm1pbmVjcmFmdDpodXNrAwNyaWReAQpzdW1tb25hYmxlAQAIA2JpZAABDGV4cGVyaW1lbnRhbAABC2hhc3NwYXduZWdnAQgCaWQPbWluZWNyYWZ0OnN0cmF5AwNyaWRcAQpzdW1tb25hYmxlAQAIA2JpZAABDGV4cGVyaW1lbnRhbAABC2hhc3NwYXduZWdnAQgCaWQPbWluZWNyYWZ0OndpdGNoAwNyaWRaAQpzdW1tb25hYmxlAQAIA2JpZAABDGV4cGVyaW1lbnRhbAABC2hhc3NwYXduZWdnAAgCaWQZbWluZWNyYWZ0OnpvbWJpZV92aWxsYWdlcgMDcmlkWAEKc3VtbW9uYWJsZQEACANiaWQAAQxleHBlcmltZW50YWwAAQtoYXNzcGF3bmVnZwEIAmlkD21pbmVjcmFmdDpibGF6ZQMDcmlkVgEKc3VtbW9uYWJsZQEACANiaWQAAQxleHBlcmltZW50YWwAAQtoYXNzcGF3bmVnZwEIAmlkFG1pbmVjcmFmdDptYWdtYV9jdWJlAwNyaWRUAQpzdW1tb25hYmxlAQAIA2JpZAABDGV4cGVyaW1lbnRhbAABC2hhc3NwYXduZWdnAQgCaWQPbWluZWNyYWZ0OmdoYXN0AwNyaWRSAQpzdW1tb25hYmxlAQAIA2JpZAABDGV4cGVyaW1lbnRhbAABC2hhc3NwYXduZWdnAQgCaWQVbWluZWNyYWZ0OmNhdmVfc3BpZGVyAwNyaWRQAQpzdW1tb25hYmxlAQAIA2JpZAABDGV4cGVyaW1lbnRhbAABC2hhc3NwYXduZWdnAQgCaWQUbWluZWNyYWZ0OnNpbHZlcmZpc2gDA3JpZE4BCnN1bW1vbmFibGUBAAgDYmlkAAEMZXhwZXJpbWVudGFsAAELaGFzc3Bhd25lZ2cBCAJpZBJtaW5lY3JhZnQ6ZW5kZXJtYW4DA3JpZEwBCnN1bW1vbmFibGUBAAgDYmlkAAEMZXhwZXJpbWVudGFsAAELaGFzc3Bhd25lZ2cBCAJpZA9taW5lY3JhZnQ6c2xpbWUDA3JpZEoBCnN1bW1vbmFibGUBAAgDYmlkAAEMZXhwZXJpbWVudGFsAAELaGFzc3Bhd25lZ2cBCAJpZBdtaW5lY3JhZnQ6em9tYmllX3BpZ21hbgMDcmlkSAEKc3VtbW9uYWJsZQEACANiaWQAAQxleHBlcmltZW50YWwAAQtoYXNzcGF3bmVnZwEIAmlkEG1pbmVjcmFmdDpzcGlkZXIDA3JpZEYBCnN1bW1vbmFibGUBAAgDYmlkAAEMZXhwZXJpbWVudGFsAAELaGFzc3Bhd25lZ2cBCAJpZBJtaW5lY3JhZnQ6c2tlbGV0b24DA3JpZEQBCnN1bW1vbmFibGUBAAgDYmlkAAEMZXhwZXJpbWVudGFsAAELaGFzc3Bhd25lZ2cBCAJpZBFtaW5lY3JhZnQ6Y3JlZXBlcgMDcmlkQgEKc3VtbW9uYWJsZQEACANiaWQAAQxleHBlcmltZW50YWwAAQtoYXNzcGF3bmVnZwEIAmlkEG1pbmVjcmFmdDp6b21iaWUDA3JpZEABCnN1bW1vbmFibGUBAAgDYmlkAAEMZXhwZXJpbWVudGFsAAELaGFzc3Bhd25lZ2cBCAJpZBhtaW5lY3JhZnQ6c2tlbGV0b25faG9yc2UDA3JpZDQBCnN1bW1vbmFibGUBAAgDYmlkAAEMZXhwZXJpbWVudGFsAAELaGFzc3Bhd25lZ2cBCAJpZA5taW5lY3JhZnQ6bXVsZQMDcmlkMgEKc3VtbW9uYWJsZQEACANiaWQAAQxleHBlcmltZW50YWwAAQtoYXNzcGF3bmVnZwEIAmlkEG1pbmVjcmFmdDpkb25rZXkDA3JpZDABCnN1bW1vbmFibGUBAAgDYmlkAAEMZXhwZXJpbWVudGFsAAELaGFzc3Bhd25lZ2cBCAJpZBFtaW5lY3JhZnQ6ZG9scGhpbgMDcmlkPgEKc3VtbW9uYWJsZQEACANiaWQAAQxleHBlcmltZW50YWwAAQtoYXNzcGF3bmVnZwEIAmlkFm1pbmVjcmFmdDp6b21iaWVfaG9yc2UDA3JpZDYBCnN1bW1vbmFibGUBAAgDYmlkAAEMZXhwZXJpbWVudGFsAAELaGFzc3Bhd25lZ2cBCAJpZBBtaW5lY3JhZnQ6dHVydGxlAwNyaWSUAQEKc3VtbW9uYWJsZQEACANiaWQAAQxleHBlcmltZW50YWwAAQtoYXNzcGF3bmVnZwEIAmlkE21pbmVjcmFmdDptb29zaHJvb20DA3JpZCABCnN1bW1vbmFibGUBAAgDYmlkAAEMZXhwZXJpbWVudGFsAAELaGFzc3Bhd25lZ2cBCAJpZA9taW5lY3JhZnQ6cGFuZGEDA3JpZOIBAQpzdW1tb25hYmxlAQAIA2JpZAABDGV4cGVyaW1lbnRhbAABC2hhc3NwYXduZWdnAQgCaWQPbWluZWNyYWZ0OmhvcnNlAwNyaWQuAQpzdW1tb25hYmxlAQAIA2JpZAABDGV4cGVyaW1lbnRhbAABC2hhc3NwYXduZWdnAQgCaWQQbWluZWNyYWZ0OnNhbG1vbgMDcmlk2gEBCnN1bW1vbmFibGUBAAgDYmlkAAEMZXhwZXJpbWVudGFsAAELaGFzc3Bhd25lZ2cBCAJpZA1taW5lY3JhZnQ6cGlnAwNyaWQYAQpzdW1tb25hYmxlAQAIA2JpZAABDGV4cGVyaW1lbnRhbAABC2hhc3NwYXduZWdnAAgCaWQSbWluZWNyYWZ0OnZpbGxhZ2VyAwNyaWQeAQpzdW1tb25hYmxlAQAIA2JpZAABDGV4cGVyaW1lbnRhbAABC2hhc3NwYXduZWdnAQgCaWQNbWluZWNyYWZ0OmNvZAMDcmlk4AEBCnN1bW1vbmFibGUBAAgDYmlkAAEMZXhwZXJpbWVudGFsAAELaGFzc3Bhd25lZ2cBCAJpZBRtaW5lY3JhZnQ6cHVmZmVyZmlzaAMDcmlk2AEBCnN1bW1vbmFibGUBAAgDYmlkAAEMZXhwZXJpbWVudGFsAAELaGFzc3Bhd25lZ2cBCAJpZA5taW5lY3JhZnQ6d29sZgMDcmlkHAEKc3VtbW9uYWJsZQEACANiaWQAAQxleHBlcmltZW50YWwAAQtoYXNzcGF3bmVnZwEIAmlkFm1pbmVjcmFmdDp0cm9waWNhbGZpc2gDA3JpZN4BAQpzdW1tb25hYmxlAQAIA2JpZAABDGV4cGVyaW1lbnRhbAABC2hhc3NwYXduZWdnAQgCaWQPbWluZWNyYWZ0OnNoZWVwAwNyaWQaAQpzdW1tb25hYmxlAQAIA2JpZAABDGV4cGVyaW1lbnRhbAABC2hhc3NwYXduZWdnAQgCaWQRbWluZWNyYWZ0OmRyb3duZWQDA3JpZNwBAQpzdW1tb25hYmxlAQAIA2JpZAABDGV4cGVyaW1lbnRhbAABC2hhc3NwYXduZWdnAQgCaWQRbWluZWNyYWZ0OmNoaWNrZW4DA3JpZBQBCnN1bW1vbmFibGUBAAgDYmlkAAEMZXhwZXJpbWVudGFsAAELaGFzc3Bhd25lZ2cBCAJpZA9taW5lY3JhZnQ6bGxhbWEDA3JpZDoBCnN1bW1vbmFibGUBAAgDYmlkAAEMZXhwZXJpbWVudGFsAAELaGFzc3Bhd25lZ2cBCAJpZA9taW5lY3JhZnQ6c3F1aWQDA3JpZCIBCnN1bW1vbmFibGUBAAgDYmlkAAEMZXhwZXJpbWVudGFsAAELaGFzc3Bhd25lZ2cBCAJpZBJtaW5lY3JhZnQ6cGlsbGFnZXIDA3JpZOQBAQpzdW1tb25hYmxlAQAIA2JpZAABDGV4cGVyaW1lbnRhbAABC2hhc3NwYXduZWdnAAgCaWQUbWluZWNyYWZ0Omlyb25fZ29sZW0DA3JpZCgBCnN1bW1vbmFibGUBAAgDYmlkAAEMZXhwZXJpbWVudGFsAAELaGFzc3Bhd25lZ2cBCAJpZBBtaW5lY3JhZnQ6cmFiYml0AwNyaWQkAQpzdW1tb25hYmxlAQAIA2JpZAABDGV4cGVyaW1lbnRhbAABC2hhc3NwYXduZWdnAQgCaWQVbWluZWNyYWZ0OnZpbGxhZ2VyX3YyAwNyaWTmAQEKc3VtbW9uYWJsZQAACANiaWQAAQxleHBlcmltZW50YWwAAQtoYXNzcGF3bmVnZwAIAmlkFG1pbmVjcmFmdDpzbm93X2dvbGVtAwNyaWQqAQpzdW1tb25hYmxlAQAIA2JpZAABDGV4cGVyaW1lbnRhbAABC2hhc3NwYXduZWdnAQgCaWQNbWluZWNyYWZ0OmJhdAMDcmlkJgEKc3VtbW9uYWJsZQEACANiaWQAAQxleHBlcmltZW50YWwAAQtoYXNzcGF3bmVnZwEIAmlkHG1pbmVjcmFmdDp6b21iaWVfdmlsbGFnZXJfdjIDA3JpZOgBAQpzdW1tb25hYmxlAAAIA2JpZAABDGV4cGVyaW1lbnRhbAABC2hhc3NwYXduZWdnAQgCaWQQbWluZWNyYWZ0Om9jZWxvdAMDcmlkLAEKc3VtbW9uYWJsZQEACANiaWQAAQxleHBlcmltZW50YWwAAQtoYXNzcGF3bmVnZwEIAmlkDW1pbmVjcmFmdDpjYXQDA3JpZJYBAQpzdW1tb25hYmxlAQAIA2JpZAABDGV4cGVyaW1lbnRhbAABC2hhc3NwYXduZWdnAQgCaWQUbWluZWNyYWZ0OnBvbGFyX2JlYXIDA3JpZDgBCnN1bW1vbmFibGUBAAgDYmlkAAEMZXhwZXJpbWVudGFsAAELaGFzc3Bhd25lZ2cBCAJpZBBtaW5lY3JhZnQ6cGFycm90AwNyaWQ8AQpzdW1tb25hYmxlAQAIA2JpZAABDGV4cGVyaW1lbnRhbAABC2hhc3NwYXduZWdnAQgCaWQZbWluZWNyYWZ0OndpdGhlcl9za2VsZXRvbgMDcmlkYAEKc3VtbW9uYWJsZQEACANiaWQAAQxleHBlcmltZW50YWwAAQtoYXNzcGF3bmVnZwEIAmlkEm1pbmVjcmFmdDpndWFyZGlhbgMDcmlkYgEKc3VtbW9uYWJsZQEACANiaWQAAQxleHBlcmltZW50YWwAAQtoYXNzcGF3bmVnZwEIAmlkGG1pbmVjcmFmdDplbGRlcl9ndWFyZGlhbgMDcmlkZAEKc3VtbW9uYWJsZQEACANiaWQAAQxleHBlcmltZW50YWwAAQtoYXNzcGF3bmVnZwEIAmlkFG1pbmVjcmFmdDp2aW5kaWNhdG9yAwNyaWRyAQpzdW1tb25hYmxlAQAIA2JpZAptaW5lY3JhZnQ6AQxleHBlcmltZW50YWwAAQtoYXNzcGF3bmVnZwAIAmlkEG1pbmVjcmFmdDpwbGF5ZXIDA3JpZIIEAQpzdW1tb25hYmxlAAAIA2JpZAABDGV4cGVyaW1lbnRhbAABC2hhc3NwYXduZWdnAQgCaWQRbWluZWNyYWZ0OnBoYW50b20DA3JpZHQBCnN1bW1vbmFibGUBAAgDYmlkCm1pbmVjcmFmdDoBDGV4cGVyaW1lbnRhbAABC2hhc3NwYXduZWdnAAgCaWQXbWluZWNyYWZ0OnRyaXBvZF9jYW1lcmEDA3JpZIQEAQpzdW1tb25hYmxlAAAIA2JpZAABDGV4cGVyaW1lbnRhbAABC2hhc3NwYXduZWdnAQgCaWQRbWluZWNyYWZ0OnJhdmFnZXIDA3JpZHYBCnN1bW1vbmFibGUBAAgDYmlkAAEMZXhwZXJpbWVudGFsAAELaGFzc3Bhd25lZ2cACAJpZBBtaW5lY3JhZnQ6d2l0aGVyAwNyaWRoAQpzdW1tb25hYmxlAQAIA2JpZAABDGV4cGVyaW1lbnRhbAABC2hhc3NwYXduZWdnAAgCaWQWbWluZWNyYWZ0OmVuZGVyX2RyYWdvbgMDcmlkagEKc3VtbW9uYWJsZQEACANiaWQAAQxleHBlcmltZW50YWwAAQtoYXNzcGF3bmVnZwEIAmlkEW1pbmVjcmFmdDpzaHVsa2VyAwNyaWRsAQpzdW1tb25hYmxlAQAIA2JpZAABDGV4cGVyaW1lbnRhbAABC2hhc3NwYXduZWdnAQgCaWQTbWluZWNyYWZ0OmVuZGVybWl0ZQMDcmlkbgEKc3VtbW9uYWJsZQEACANiaWQAAQxleHBlcmltZW50YWwAAQtoYXNzcGF3bmVnZwAIAmlkEm1pbmVjcmFmdDptaW5lY2FydAMDcmlkqAEBCnN1bW1vbmFibGUBAAgDYmlkAAEMZXhwZXJpbWVudGFsAAELaGFzc3Bhd25lZ2cACAJpZBltaW5lY3JhZnQ6aG9wcGVyX21pbmVjYXJ0AwNyaWTAAQEKc3VtbW9uYWJsZQEACANiaWQAAQxleHBlcmltZW50YWwAAQtoYXNzcGF3bmVnZwAIAmlkFm1pbmVjcmFmdDp0bnRfbWluZWNhcnQDA3JpZMIBAQpzdW1tb25hYmxlAQAIA2JpZAABDGV4cGVyaW1lbnRhbAABC2hhc3NwYXduZWdnAAgCaWQYbWluZWNyYWZ0OmNoZXN0X21pbmVjYXJ0AwNyaWTEAQEKc3VtbW9uYWJsZQEACANiaWQAAQxleHBlcmltZW50YWwAAQtoYXNzcGF3bmVnZwAIAmlkIG1pbmVjcmFmdDpjb21tYW5kX2Jsb2NrX21pbmVjYXJ0AwNyaWTIAQEKc3VtbW9uYWJsZQEACANiaWQAAQxleHBlcmltZW50YWwAAQtoYXNzcGF3bmVnZwAIAmlkFW1pbmVjcmFmdDphcm1vcl9zdGFuZAMDcmlkegEKc3VtbW9uYWJsZQEACANiaWQAAQxleHBlcmltZW50YWwAAQtoYXNzcGF3bmVnZwAIAmlkDm1pbmVjcmFmdDppdGVtAwNyaWSAAQEKc3VtbW9uYWJsZQAACANiaWQAAQxleHBlcmltZW50YWwAAQtoYXNzcGF3bmVnZwAIAmlkDW1pbmVjcmFmdDp0bnQDA3JpZIIBAQpzdW1tb25hYmxlAQAIA2JpZAABDGV4cGVyaW1lbnRhbAABC2hhc3NwYXduZWdnAAgCaWQXbWluZWNyYWZ0OmZhbGxpbmdfYmxvY2sDA3JpZIQBAQpzdW1tb25hYmxlAAAIA2JpZAABDGV4cGVyaW1lbnRhbAABC2hhc3NwYXduZWdnAAgCaWQTbWluZWNyYWZ0OnhwX2JvdHRsZQMDcmlkiAEBCnN1bW1vbmFibGUBAAgDYmlkAAEMZXhwZXJpbWVudGFsAAELaGFzc3Bhd25lZ2cACAJpZBBtaW5lY3JhZnQ6eHBfb3JiAwNyaWSKAQEKc3VtbW9uYWJsZQEACANiaWQAAQxleHBlcmltZW50YWwAAQtoYXNzcGF3bmVnZwAIAmlkHW1pbmVjcmFmdDpleWVfb2ZfZW5kZXJfc2lnbmFsAwNyaWSMAQEKc3VtbW9uYWJsZQAACANiaWQAAQxleHBlcmltZW50YWwAAQtoYXNzcGF3bmVnZwAIAmlkF21pbmVjcmFmdDplbmRlcl9jcnlzdGFsAwNyaWSOAQEKc3VtbW9uYWJsZQEACANiaWQAAQxleHBlcmltZW50YWwAAQtoYXNzcGF3bmVnZwAIAmlkGG1pbmVjcmFmdDpzaHVsa2VyX2J1bGxldAMDcmlkmAEBCnN1bW1vbmFibGUAAAgDYmlkAAEMZXhwZXJpbWVudGFsAAELaGFzc3Bhd25lZ2cACAJpZBZtaW5lY3JhZnQ6ZmlzaGluZ19ob29rAwNyaWSaAQEKc3VtbW9uYWJsZQAACANiaWQAAQxleHBlcmltZW50YWwAAQtoYXNzcGF3bmVnZwAIAmlkGW1pbmVjcmFmdDpkcmFnb25fZmlyZWJhbGwDA3JpZJ4BAQpzdW1tb25hYmxlAAAIA2JpZAABDGV4cGVyaW1lbnRhbAABC2hhc3NwYXduZWdnAAgCaWQPbWluZWNyYWZ0OmFycm93AwNyaWSgAQEKc3VtbW9uYWJsZQEACANiaWQAAQxleHBlcmltZW50YWwAAQtoYXNzcGF3bmVnZwAIAmlkEm1pbmVjcmFmdDpzbm93YmFsbAMDcmlkogEBCnN1bW1vbmFibGUBAAgDYmlkAAEMZXhwZXJpbWVudGFsAAELaGFzc3Bhd25lZ2cACAJpZA1taW5lY3JhZnQ6ZWdnAwNyaWSkAQEKc3VtbW9uYWJsZQEACANiaWQAAQxleHBlcmltZW50YWwAAQtoYXNzcGF3bmVnZwAIAmlkEm1pbmVjcmFmdDpwYWludGluZwMDcmlkpgEBCnN1bW1vbmFibGUAAAgDYmlkAAEMZXhwZXJpbWVudGFsAAELaGFzc3Bhd25lZ2cACAJpZBhtaW5lY3JhZnQ6dGhyb3duX3RyaWRlbnQDA3JpZJIBAQpzdW1tb25hYmxlAAAIA2JpZAABDGV4cGVyaW1lbnRhbAABC2hhc3NwYXduZWdnAAgCaWQSbWluZWNyYWZ0OmZpcmViYWxsAwNyaWSqAQEKc3VtbW9uYWJsZQAACANiaWQAAQxleHBlcmltZW50YWwAAQtoYXNzcGF3bmVnZwAIAmlkF21pbmVjcmFmdDpzcGxhc2hfcG90aW9uAwNyaWSsAQEKc3VtbW9uYWJsZQEACANiaWQAAQxleHBlcmltZW50YWwAAQtoYXNzcGF3bmVnZwAIAmlkFW1pbmVjcmFmdDplbmRlcl9wZWFybAMDcmlkrgEBCnN1bW1vbmFibGUAAAgDYmlkAAEMZXhwZXJpbWVudGFsAAELaGFzc3Bhd25lZ2cACAJpZBRtaW5lY3JhZnQ6bGVhc2hfa25vdAMDcmlksAEBCnN1bW1vbmFibGUBAAgDYmlkAAEMZXhwZXJpbWVudGFsAAELaGFzc3Bhd25lZ2cACAJpZBZtaW5lY3JhZnQ6d2l0aGVyX3NrdWxsAwNyaWSyAQEKc3VtbW9uYWJsZQAACANiaWQAAQxleHBlcmltZW50YWwAAQtoYXNzcGF3bmVnZwAIAmlkIG1pbmVjcmFmdDp3aXRoZXJfc2t1bGxfZGFuZ2Vyb3VzAwNyaWS2AQEKc3VtbW9uYWJsZQAACANiaWQAAQxleHBlcmltZW50YWwAAQtoYXNzcGF3bmVnZwAIAmlkDm1pbmVjcmFmdDpib2F0AwNyaWS0AQEKc3VtbW9uYWJsZQEACANiaWQAAQxleHBlcmltZW50YWwAAQtoYXNzcGF3bmVnZwAIAmlkGG1pbmVjcmFmdDpsaWdodG5pbmdfYm9sdAMDcmlkugEBCnN1bW1vbmFibGUBAAgDYmlkAAEMZXhwZXJpbWVudGFsAAELaGFzc3Bhd25lZ2cACAJpZBhtaW5lY3JhZnQ6c21hbGxfZmlyZWJhbGwDA3JpZLwBAQpzdW1tb25hYmxlAAAIA2JpZAABDGV4cGVyaW1lbnRhbAABC2hhc3NwYXduZWdnAAgCaWQUbWluZWNyYWZ0OmxsYW1hX3NwaXQDA3JpZMwBAQpzdW1tb25hYmxlAAAIA2JpZAABDGV4cGVyaW1lbnRhbAABC2hhc3NwYXduZWdnAAgCaWQbbWluZWNyYWZ0OmFyZWFfZWZmZWN0X2Nsb3VkAwNyaWS+AQEKc3VtbW9uYWJsZQAACANiaWQAAQxleHBlcmltZW50YWwAAQtoYXNzcGF3bmVnZwAIAmlkGm1pbmVjcmFmdDpsaW5nZXJpbmdfcG90aW9uAwNyaWTKAQEKc3VtbW9uYWJsZQAACANiaWQAAQxleHBlcmltZW50YWwAAQtoYXNzcGF3bmVnZwAIAmlkGm1pbmVjcmFmdDpmaXJld29ya3Nfcm9ja2V0AwNyaWSQAQEKc3VtbW9uYWJsZQEACANiaWQAAQxleHBlcmltZW50YWwAAQtoYXNzcGF3bmVnZwAIAmlkGG1pbmVjcmFmdDpldm9jYXRpb25fZmFuZwMDcmlkzgEBCnN1bW1vbmFibGUBAAgDYmlkAAEMZXhwZXJpbWVudGFsAAELaGFzc3Bhd25lZ2cBCAJpZBttaW5lY3JhZnQ6ZXZvY2F0aW9uX2lsbGFnZXIDA3JpZNABAQpzdW1tb25hYmxlAQAIA2JpZAABDGV4cGVyaW1lbnRhbAABC2hhc3NwYXduZWdnAQgCaWQNbWluZWNyYWZ0OnZleAMDcmlk0gEBCnN1bW1vbmFibGUBAAgDYmlkAAEMZXhwZXJpbWVudGFsAAELaGFzc3Bhd25lZ2cACAJpZA9taW5lY3JhZnQ6YWdlbnQDA3JpZHABCnN1bW1vbmFibGUAAAA="; + /** @var string|null */ + private static $DEFAULT_NBT_CACHE = null; + /** @var string */ public $namedtag; @@ -44,7 +43,11 @@ class AvailableActorIdentifiersPacket extends DataPacket{ } protected function encodePayload(){ - $this->put($this->namedtag ?? base64_decode(self::HARDCODED_NBT_BLOB)); + $this->put( + $this->namedtag ?? + self::$DEFAULT_NBT_CACHE ?? + (self::$DEFAULT_NBT_CACHE = file_get_contents(\pocketmine\RESOURCE_PATH . '/vanilla/entity_identifiers.nbt')) + ); } public function handle(NetworkSession $session) : bool{ diff --git a/src/pocketmine/network/mcpe/protocol/AvailableCommandsPacket.php b/src/pocketmine/network/mcpe/protocol/AvailableCommandsPacket.php index d938ad485..d4ef733e9 100644 --- a/src/pocketmine/network/mcpe/protocol/AvailableCommandsPacket.php +++ b/src/pocketmine/network/mcpe/protocol/AvailableCommandsPacket.php @@ -28,6 +28,7 @@ namespace pocketmine\network\mcpe\protocol; use pocketmine\network\mcpe\NetworkSession; use pocketmine\network\mcpe\protocol\types\CommandData; use pocketmine\network\mcpe\protocol\types\CommandEnum; +use pocketmine\network\mcpe\protocol\types\CommandEnumConstraint; use pocketmine\network\mcpe\protocol\types\CommandParameter; use pocketmine\utils\BinaryDataException; use function count; @@ -56,17 +57,17 @@ class AvailableCommandsPacket extends DataPacket{ public const ARG_TYPE_FILEPATH = 0x0e; - public const ARG_TYPE_STRING = 0x1b; + public const ARG_TYPE_STRING = 0x1d; - public const ARG_TYPE_POSITION = 0x1d; + public const ARG_TYPE_POSITION = 0x25; - public const ARG_TYPE_MESSAGE = 0x20; + public const ARG_TYPE_MESSAGE = 0x29; - public const ARG_TYPE_RAWTEXT = 0x22; + public const ARG_TYPE_RAWTEXT = 0x2b; - public const ARG_TYPE_JSON = 0x25; + public const ARG_TYPE_JSON = 0x2f; - public const ARG_TYPE_COMMAND = 0x2c; + public const ARG_TYPE_COMMAND = 0x36; /** * Enums are a little different: they are composed as follows: @@ -79,12 +80,23 @@ class AvailableCommandsPacket extends DataPacket{ */ public const ARG_FLAG_POSTFIX = 0x1000000; + public const HARDCODED_ENUM_NAMES = [ + "CommandName" => true + ]; + /** * @var CommandData[] * List of command data, including name, description, alias indexes and parameters. */ public $commandData = []; + /** + * @var CommandEnum[] + * List of enums which aren't directly referenced by any vanilla command. + * This is used for the `CommandName` enum, which is a magic enum used by the `command` argument type. + */ + public $hardcodedEnums = []; + /** * @var CommandEnum[] * List of dynamic command enums, also referred to as "soft" enums. These can by dynamically updated mid-game @@ -92,6 +104,12 @@ class AvailableCommandsPacket extends DataPacket{ */ public $softEnums = []; + /** + * @var CommandEnumConstraint[] + * List of constraints for enum members. Used to constrain gamerules that can bechanged in nocheats mode and more. + */ + public $enumConstraints = []; + protected function decodePayload(){ /** @var string[] $enumValues */ $enumValues = []; @@ -108,7 +126,10 @@ class AvailableCommandsPacket extends DataPacket{ /** @var CommandEnum[] $enums */ $enums = []; for($i = 0, $count = $this->getUnsignedVarInt(); $i < $count; ++$i){ - $enums[] = $this->getEnum($enumValues); + $enums[] = $enum = $this->getEnum($enumValues); + if(isset(self::HARDCODED_ENUM_NAMES[$enum->enumName])){ + $this->hardcodedEnums[] = $enum; + } } for($i = 0, $count = $this->getUnsignedVarInt(); $i < $count; ++$i){ @@ -118,6 +139,10 @@ class AvailableCommandsPacket extends DataPacket{ for($i = 0, $count = $this->getUnsignedVarInt(); $i < $count; ++$i){ $this->softEnums[] = $this->getSoftEnum(); } + + for($i = 0, $count = $this->getUnsignedVarInt(); $i < $count; ++$i){ + $this->enumConstraints[] = $this->getEnumConstraint($enums, $enumValues); + } } /** @@ -210,6 +235,50 @@ class AvailableCommandsPacket extends DataPacket{ } } + /** + * @param CommandEnum[] $enums + * @param string[] $enumValues + * + * @return CommandEnumConstraint + */ + protected function getEnumConstraint(array $enums, array $enumValues) : CommandEnumConstraint{ + //wtf, what was wrong with an offset inside the enum? :( + $valueIndex = $this->getLInt(); + if(!isset($enumValues[$valueIndex])){ + throw new \UnexpectedValueException("Enum constraint refers to unknown enum value index $valueIndex"); + } + $enumIndex = $this->getLInt(); + if(!isset($enums[$enumIndex])){ + throw new \UnexpectedValueException("Enum constraint refers to unknown enum index $enumIndex"); + } + $enum = $enums[$enumIndex]; + $valueOffset = array_search($enumValues[$valueIndex], $enum->enumValues, true); + if($valueOffset === false){ + throw new \UnexpectedValueException("Value \"" . $enumValues[$valueIndex] . "\" does not belong to enum \"$enum->enumName\""); + } + + $constraintIds = []; + for($i = 0, $count = $this->getUnsignedVarInt(); $i < $count; ++$i){ + $constraintIds[] = $this->getByte(); + } + + return new CommandEnumConstraint($enum, $valueOffset, $constraintIds); + } + + /** + * @param CommandEnumConstraint $constraint + * @param int[] $enumIndexes string enum name -> int index + * @param int[] $enumValueIndexes string value -> int index + */ + protected function putEnumConstraint(CommandEnumConstraint $constraint, array $enumIndexes, array $enumValueIndexes) : void{ + $this->putLInt($enumValueIndexes[$constraint->getAffectedValue()]); + $this->putLInt($enumIndexes[$constraint->getEnum()->enumName]); + $this->putUnsignedVarInt(count($constraint->getConstraints())); + foreach($constraint->getConstraints() as $v){ + $this->putByte($v); + } + } + /** * @param CommandEnum[] $enums * @param string[] $postfixes @@ -349,27 +418,28 @@ class AvailableCommandsPacket extends DataPacket{ $enumIndexes = []; /** @var CommandEnum[] $enums */ $enums = []; + + $addEnumFn = static function(CommandEnum $enum) use (&$enums, &$enumIndexes, &$enumValueIndexes){ + if(!isset($enumIndexes[$enum->enumName])){ + $enums[$enumIndexes[$enum->enumName] = count($enumIndexes)] = $enum; + } + foreach($enum->enumValues as $str){ + $enumValueIndexes[$str] = $enumValueIndexes[$str] ?? count($enumValueIndexes); + } + }; + foreach($this->hardcodedEnums as $enum){ + $addEnumFn($enum); + } foreach($this->commandData as $commandData){ if($commandData->aliases !== null){ - if(!isset($enumIndexes[$commandData->aliases->enumName])){ - $enums[$enumIndexes[$commandData->aliases->enumName] = count($enumIndexes)] = $commandData->aliases; - } - - foreach($commandData->aliases->enumValues as $str){ - $enumValueIndexes[$str] = $enumValueIndexes[$str] ?? count($enumValueIndexes); //latest index - } + $addEnumFn($commandData->aliases); } /** @var CommandParameter[] $overload */ foreach($commandData->overloads as $overload){ /** @var CommandParameter $parameter */ foreach($overload as $parameter){ if($parameter->enum !== null){ - if(!isset($enumIndexes[$parameter->enum->enumName])){ - $enums[$enumIndexes[$parameter->enum->enumName] = count($enumIndexes)] = $parameter->enum; - } - foreach($parameter->enum->enumValues as $str){ - $enumValueIndexes[$str] = $enumValueIndexes[$str] ?? count($enumValueIndexes); - } + $addEnumFn($parameter->enum); } if($parameter->postfix !== null){ @@ -403,6 +473,11 @@ class AvailableCommandsPacket extends DataPacket{ foreach($this->softEnums as $enum){ $this->putSoftEnum($enum); } + + $this->putUnsignedVarInt(count($this->enumConstraints)); + foreach($this->enumConstraints as $constraint){ + $this->putEnumConstraint($constraint, $enumIndexes, $enumValueIndexes); + } } public function handle(NetworkSession $session) : bool{ diff --git a/src/pocketmine/network/mcpe/protocol/BiomeDefinitionListPacket.php b/src/pocketmine/network/mcpe/protocol/BiomeDefinitionListPacket.php index 7a132e991..32599533a 100644 --- a/src/pocketmine/network/mcpe/protocol/BiomeDefinitionListPacket.php +++ b/src/pocketmine/network/mcpe/protocol/BiomeDefinitionListPacket.php @@ -26,11 +26,14 @@ namespace pocketmine\network\mcpe\protocol; #include use pocketmine\network\mcpe\NetworkSession; +use function file_get_contents; class BiomeDefinitionListPacket extends DataPacket{ public const NETWORK_ID = ProtocolInfo::BIOME_DEFINITION_LIST_PACKET; - public const HARDCODED_NBT_BLOB = "CgAKDWJhbWJvb19qdW5nbGUFCGRvd25mYWxsZmZmPwULdGVtcGVyYXR1cmUzM3M/AAoTYmFtYm9vX2p1bmdsZV9oaWxscwUIZG93bmZhbGxmZmY/BQt0ZW1wZXJhdHVyZTMzcz8ACgViZWFjaAUIZG93bmZhbGzNzMw+BQt0ZW1wZXJhdHVyZc3MTD8ACgxiaXJjaF9mb3Jlc3QFCGRvd25mYWxsmpkZPwULdGVtcGVyYXR1cmWamRk/AAoSYmlyY2hfZm9yZXN0X2hpbGxzBQhkb3duZmFsbJqZGT8FC3RlbXBlcmF0dXJlmpkZPwAKGmJpcmNoX2ZvcmVzdF9oaWxsc19tdXRhdGVkBQhkb3duZmFsbM3MTD8FC3RlbXBlcmF0dXJlMzMzPwAKFGJpcmNoX2ZvcmVzdF9tdXRhdGVkBQhkb3duZmFsbM3MTD8FC3RlbXBlcmF0dXJlMzMzPwAKCmNvbGRfYmVhY2gFCGRvd25mYWxsmpmZPgULdGVtcGVyYXR1cmXNzEw9AAoKY29sZF9vY2VhbgUIZG93bmZhbGwAAAA/BQt0ZW1wZXJhdHVyZQAAAD8ACgpjb2xkX3RhaWdhBQhkb3duZmFsbM3MzD4FC3RlbXBlcmF0dXJlAAAAvwAKEGNvbGRfdGFpZ2FfaGlsbHMFCGRvd25mYWxszczMPgULdGVtcGVyYXR1cmUAAAC/AAoSY29sZF90YWlnYV9tdXRhdGVkBQhkb3duZmFsbM3MzD4FC3RlbXBlcmF0dXJlAAAAvwAKD2RlZXBfY29sZF9vY2VhbgUIZG93bmZhbGwAAAA/BQt0ZW1wZXJhdHVyZQAAAD8AChFkZWVwX2Zyb3plbl9vY2VhbgUIZG93bmZhbGwAAAA/BQt0ZW1wZXJhdHVyZQAAAAAAChNkZWVwX2x1a2V3YXJtX29jZWFuBQhkb3duZmFsbAAAAD8FC3RlbXBlcmF0dXJlAAAAPwAKCmRlZXBfb2NlYW4FCGRvd25mYWxsAAAAPwULdGVtcGVyYXR1cmUAAAA/AAoPZGVlcF93YXJtX29jZWFuBQhkb3duZmFsbAAAAD8FC3RlbXBlcmF0dXJlAAAAPwAKBmRlc2VydAUIZG93bmZhbGwAAAAABQt0ZW1wZXJhdHVyZQAAAEAACgxkZXNlcnRfaGlsbHMFCGRvd25mYWxsAAAAAAULdGVtcGVyYXR1cmUAAABAAAoOZGVzZXJ0X211dGF0ZWQFCGRvd25mYWxsAAAAAAULdGVtcGVyYXR1cmUAAABAAAoNZXh0cmVtZV9oaWxscwUIZG93bmZhbGyamZk+BQt0ZW1wZXJhdHVyZc3MTD4AChJleHRyZW1lX2hpbGxzX2VkZ2UFCGRvd25mYWxsmpmZPgULdGVtcGVyYXR1cmXNzEw+AAoVZXh0cmVtZV9oaWxsc19tdXRhdGVkBQhkb3duZmFsbJqZmT4FC3RlbXBlcmF0dXJlzcxMPgAKGGV4dHJlbWVfaGlsbHNfcGx1c190cmVlcwUIZG93bmZhbGyamZk+BQt0ZW1wZXJhdHVyZc3MTD4ACiBleHRyZW1lX2hpbGxzX3BsdXNfdHJlZXNfbXV0YXRlZAUIZG93bmZhbGyamZk+BQt0ZW1wZXJhdHVyZc3MTD4ACg1mbG93ZXJfZm9yZXN0BQhkb3duZmFsbM3MTD8FC3RlbXBlcmF0dXJlMzMzPwAKBmZvcmVzdAUIZG93bmZhbGzNzEw/BQt0ZW1wZXJhdHVyZTMzMz8ACgxmb3Jlc3RfaGlsbHMFCGRvd25mYWxszcxMPwULdGVtcGVyYXR1cmUzMzM/AAoMZnJvemVuX29jZWFuBQhkb3duZmFsbAAAAD8FC3RlbXBlcmF0dXJlAAAAAAAKDGZyb3plbl9yaXZlcgUIZG93bmZhbGwAAAA/BQt0ZW1wZXJhdHVyZQAAAAAACgRoZWxsBQhkb3duZmFsbAAAAAAFC3RlbXBlcmF0dXJlAAAAQAAKDWljZV9tb3VudGFpbnMFCGRvd25mYWxsAAAAPwULdGVtcGVyYXR1cmUAAAAAAAoKaWNlX3BsYWlucwUIZG93bmZhbGwAAAA/BQt0ZW1wZXJhdHVyZQAAAAAAChFpY2VfcGxhaW5zX3NwaWtlcwUIZG93bmZhbGwAAIA/BQt0ZW1wZXJhdHVyZQAAAAAACgZqdW5nbGUFCGRvd25mYWxsZmZmPwULdGVtcGVyYXR1cmUzM3M/AAoLanVuZ2xlX2VkZ2UFCGRvd25mYWxszcxMPwULdGVtcGVyYXR1cmUzM3M/AAoTanVuZ2xlX2VkZ2VfbXV0YXRlZAUIZG93bmZhbGzNzEw/BQt0ZW1wZXJhdHVyZTMzcz8ACgxqdW5nbGVfaGlsbHMFCGRvd25mYWxsZmZmPwULdGVtcGVyYXR1cmUzM3M/AAoOanVuZ2xlX211dGF0ZWQFCGRvd25mYWxsZmZmPwULdGVtcGVyYXR1cmUzM3M/AAoTbGVnYWN5X2Zyb3plbl9vY2VhbgUIZG93bmZhbGwAAAA/BQt0ZW1wZXJhdHVyZQAAAAAACg5sdWtld2FybV9vY2VhbgUIZG93bmZhbGwAAAA/BQt0ZW1wZXJhdHVyZQAAAD8ACgptZWdhX3RhaWdhBQhkb3duZmFsbM3MTD8FC3RlbXBlcmF0dXJlmpmZPgAKEG1lZ2FfdGFpZ2FfaGlsbHMFCGRvd25mYWxszcxMPwULdGVtcGVyYXR1cmWamZk+AAoEbWVzYQUIZG93bmZhbGwAAAAABQt0ZW1wZXJhdHVyZQAAAEAACgptZXNhX2JyeWNlBQhkb3duZmFsbAAAAAAFC3RlbXBlcmF0dXJlAAAAQAAKDG1lc2FfcGxhdGVhdQUIZG93bmZhbGwAAAAABQt0ZW1wZXJhdHVyZQAAAEAAChRtZXNhX3BsYXRlYXVfbXV0YXRlZAUIZG93bmZhbGwAAAAABQt0ZW1wZXJhdHVyZQAAAEAAChJtZXNhX3BsYXRlYXVfc3RvbmUFCGRvd25mYWxsAAAAAAULdGVtcGVyYXR1cmUAAABAAAoabWVzYV9wbGF0ZWF1X3N0b25lX211dGF0ZWQFCGRvd25mYWxsAAAAAAULdGVtcGVyYXR1cmUAAABAAAoPbXVzaHJvb21faXNsYW5kBQhkb3duZmFsbAAAgD8FC3RlbXBlcmF0dXJlZmZmPwAKFW11c2hyb29tX2lzbGFuZF9zaG9yZQUIZG93bmZhbGwAAIA/BQt0ZW1wZXJhdHVyZWZmZj8ACgVvY2VhbgUIZG93bmZhbGwAAAA/BQt0ZW1wZXJhdHVyZQAAAD8ACgZwbGFpbnMFCGRvd25mYWxszczMPgULdGVtcGVyYXR1cmXNzEw/AAobcmVkd29vZF90YWlnYV9oaWxsc19tdXRhdGVkBQhkb3duZmFsbM3MTD8FC3RlbXBlcmF0dXJlmpmZPgAKFXJlZHdvb2RfdGFpZ2FfbXV0YXRlZAUIZG93bmZhbGzNzEw/BQt0ZW1wZXJhdHVyZQAAgD4ACgVyaXZlcgUIZG93bmZhbGwAAAA/BQt0ZW1wZXJhdHVyZQAAAD8ACg1yb29mZWRfZm9yZXN0BQhkb3duZmFsbM3MTD8FC3RlbXBlcmF0dXJlMzMzPwAKFXJvb2ZlZF9mb3Jlc3RfbXV0YXRlZAUIZG93bmZhbGzNzEw/BQt0ZW1wZXJhdHVyZTMzMz8ACgdzYXZhbm5hBQhkb3duZmFsbAAAAAAFC3RlbXBlcmF0dXJlmpmZPwAKD3NhdmFubmFfbXV0YXRlZAUIZG93bmZhbGwAAAA/BQt0ZW1wZXJhdHVyZc3MjD8ACg9zYXZhbm5hX3BsYXRlYXUFCGRvd25mYWxsAAAAAAULdGVtcGVyYXR1cmUAAIA/AAoXc2F2YW5uYV9wbGF0ZWF1X211dGF0ZWQFCGRvd25mYWxsAAAAPwULdGVtcGVyYXR1cmUAAIA/AAoLc3RvbmVfYmVhY2gFCGRvd25mYWxsmpmZPgULdGVtcGVyYXR1cmXNzEw+AAoQc3VuZmxvd2VyX3BsYWlucwUIZG93bmZhbGzNzMw+BQt0ZW1wZXJhdHVyZc3MTD8ACglzd2FtcGxhbmQFCGRvd25mYWxsAAAAPwULdGVtcGVyYXR1cmXNzEw/AAoRc3dhbXBsYW5kX211dGF0ZWQFCGRvd25mYWxsAAAAPwULdGVtcGVyYXR1cmXNzEw/AAoFdGFpZ2EFCGRvd25mYWxszcxMPwULdGVtcGVyYXR1cmUAAIA+AAoLdGFpZ2FfaGlsbHMFCGRvd25mYWxszcxMPwULdGVtcGVyYXR1cmUAAIA+AAoNdGFpZ2FfbXV0YXRlZAUIZG93bmZhbGzNzEw/BQt0ZW1wZXJhdHVyZQAAgD4ACgd0aGVfZW5kBQhkb3duZmFsbAAAAD8FC3RlbXBlcmF0dXJlAAAAPwAKCndhcm1fb2NlYW4FCGRvd25mYWxsAAAAPwULdGVtcGVyYXR1cmUAAAA/AAA="; + /** @var string|null */ + private static $DEFAULT_NBT_CACHE = null; + /** @var string */ public $namedtag; @@ -39,7 +42,11 @@ class BiomeDefinitionListPacket extends DataPacket{ } protected function encodePayload(){ - $this->put($this->namedtag ?? self::HARDCODED_NBT_BLOB); + $this->put( + $this->namedtag ?? + self::$DEFAULT_NBT_CACHE ?? + (self::$DEFAULT_NBT_CACHE = file_get_contents(\pocketmine\RESOURCE_PATH . '/vanilla/biome_definitions.nbt')) + ); } public function handle(NetworkSession $session) : bool{ diff --git a/src/pocketmine/network/mcpe/protocol/CompletedUsingItemPacket.php b/src/pocketmine/network/mcpe/protocol/CompletedUsingItemPacket.php new file mode 100644 index 000000000..2a7381f23 --- /dev/null +++ b/src/pocketmine/network/mcpe/protocol/CompletedUsingItemPacket.php @@ -0,0 +1,66 @@ +itemId = $this->getShort(); + $this->action = $this->getLInt(); + } + + public function encodePayload() : void{ + $this->putShort($this->itemId); + $this->putLInt($this->action); + } + + public function handle(NetworkSession $session) : bool{ + return $session->handleCompletedUsingItem($this); + } +} \ No newline at end of file diff --git a/src/pocketmine/network/mcpe/protocol/CraftingDataPacket.php b/src/pocketmine/network/mcpe/protocol/CraftingDataPacket.php index bb5f7cd8f..a05d2bc27 100644 --- a/src/pocketmine/network/mcpe/protocol/CraftingDataPacket.php +++ b/src/pocketmine/network/mcpe/protocol/CraftingDataPacket.php @@ -33,6 +33,8 @@ use pocketmine\item\Item; use pocketmine\item\ItemFactory; use pocketmine\network\mcpe\NetworkBinaryStream; use pocketmine\network\mcpe\NetworkSession; +use pocketmine\network\mcpe\protocol\types\PotionContainerChangeRecipe; +use pocketmine\network\mcpe\protocol\types\PotionTypeRecipe; #ifndef COMPILE use pocketmine\utils\Binary; #endif @@ -53,6 +55,10 @@ class CraftingDataPacket extends DataPacket{ /** @var object[] */ public $entries = []; + /** @var PotionTypeRecipe[] */ + public $potionTypeRecipes = []; + /** @var PotionContainerChangeRecipe[] */ + public $potionContainerRecipes = []; /** @var bool */ public $cleanRecipes = false; @@ -140,6 +146,18 @@ class CraftingDataPacket extends DataPacket{ } $this->decodedEntries[] = $entry; } + for($i = 0, $count = $this->getUnsignedVarInt(); $i < $count; ++$i){ + $input = $this->getVarInt(); + $ingredient = $this->getVarInt(); + $output = $this->getVarInt(); + $this->potionTypeRecipes[] = new PotionTypeRecipe($input, $ingredient, $output); + } + for($i = 0, $count = $this->getUnsignedVarInt(); $i < $count; ++$i){ + $input = $this->getVarInt(); + $ingredient = $this->getVarInt(); + $output = $this->getVarInt(); + $this->potionContainerRecipes[] = new PotionContainerChangeRecipe($input, $ingredient, $output); + } $this->cleanRecipes = $this->getBool(); } @@ -240,6 +258,18 @@ class CraftingDataPacket extends DataPacket{ $writer->reset(); } + $this->putUnsignedVarInt(count($this->potionTypeRecipes)); + foreach($this->potionTypeRecipes as $recipe){ + $this->putVarInt($recipe->getInputPotionType()); + $this->putVarInt($recipe->getIngredientItemId()); + $this->putVarInt($recipe->getOutputPotionType()); + } + $this->putUnsignedVarInt(count($this->potionContainerRecipes)); + foreach($this->potionContainerRecipes as $recipe){ + $this->putVarInt($recipe->getInputItemId()); + $this->putVarInt($recipe->getIngredientItemId()); + $this->putVarInt($recipe->getOutputItemId()); + } $this->putBool($this->cleanRecipes); } diff --git a/src/pocketmine/network/mcpe/protocol/EducationSettingsPacket.php b/src/pocketmine/network/mcpe/protocol/EducationSettingsPacket.php new file mode 100644 index 000000000..e8f5eea4e --- /dev/null +++ b/src/pocketmine/network/mcpe/protocol/EducationSettingsPacket.php @@ -0,0 +1,66 @@ + + +use pocketmine\network\mcpe\NetworkSession; + +class EducationSettingsPacket extends DataPacket{ + public const NETWORK_ID = ProtocolInfo::EDUCATION_SETTINGS_PACKET; + + /** @var string */ + private $codeBuilderDefaultUri; + /** @var bool */ + private $hasQuiz; + + public static function create(string $codeBuilderDefaultUri, bool $hasQuiz) : self{ + $result = new self; + $result->codeBuilderDefaultUri = $codeBuilderDefaultUri; + $result->hasQuiz = $hasQuiz; + return $result; + } + + public function getCodeBuilderDefaultUri() : string{ + return $this->codeBuilderDefaultUri; + } + + public function getHasQuiz() : bool{ + return $this->hasQuiz; + } + + protected function decodePayload() : void{ + $this->codeBuilderDefaultUri = $this->getString(); + $this->hasQuiz = $this->getBool(); + } + + protected function encodePayload() : void{ + $this->putString($this->codeBuilderDefaultUri); + $this->putBool($this->hasQuiz); + } + + public function handle(NetworkSession $handler) : bool{ + return $handler->handleEducationSettings($this); + } +} diff --git a/src/pocketmine/network/mcpe/protocol/EmotePacket.php b/src/pocketmine/network/mcpe/protocol/EmotePacket.php new file mode 100644 index 000000000..b973b39bd --- /dev/null +++ b/src/pocketmine/network/mcpe/protocol/EmotePacket.php @@ -0,0 +1,81 @@ + + +use pocketmine\network\mcpe\NetworkSession; + +class EmotePacket extends DataPacket/* implements ClientboundPacket, ServerboundPacket*/{ + public const NETWORK_ID = ProtocolInfo::EMOTE_PACKET; + + private const FLAG_SERVER = 1 << 0; + + /** @var int */ + private $entityRuntimeId; + /** @var string */ + private $emoteId; + /** @var int */ + private $flags; + + public static function create(int $entityRuntimeId, string $emoteId, int $flags) : self{ + $result = new self; + $result->entityRuntimeId = $entityRuntimeId; + $result->emoteId = $emoteId; + $result->flags = $flags; + return $result; + } + + /** + * TODO: we can't call this getEntityRuntimeId() because of base class collision (crap architecture, thanks Shoghi) + * @return int + */ + public function getEntityRuntimeIdField() : int{ + return $this->entityRuntimeId; + } + + public function getEmoteId() : string{ + return $this->emoteId; + } + + public function getFlags() : int{ + return $this->flags; + } + + protected function decodePayload() : void{ + $this->entityRuntimeId = $this->getEntityRuntimeId(); + $this->emoteId = $this->getString(); + $this->flags = $this->getByte(); + } + + protected function encodePayload() : void{ + $this->putEntityRuntimeId($this->entityRuntimeId); + $this->putString($this->emoteId); + $this->putByte($this->flags); + } + + public function handle(NetworkSession $handler) : bool{ + return $handler->handleEmote($this); + } +} diff --git a/src/pocketmine/network/mcpe/protocol/ExplodePacket.php b/src/pocketmine/network/mcpe/protocol/ExplodePacket.php deleted file mode 100644 index 174d8a6bf..000000000 --- a/src/pocketmine/network/mcpe/protocol/ExplodePacket.php +++ /dev/null @@ -1,73 +0,0 @@ - - - -use pocketmine\math\Vector3; -use pocketmine\network\mcpe\NetworkSession; -use function count; - -class ExplodePacket extends DataPacket{ - public const NETWORK_ID = ProtocolInfo::EXPLODE_PACKET; - - /** @var Vector3 */ - public $position; - /** @var float */ - public $radius; - /** @var Vector3[] */ - public $records = []; - - public function clean(){ - $this->records = []; - return parent::clean(); - } - - protected function decodePayload(){ - $this->position = $this->getVector3(); - $this->radius = (float) ($this->getVarInt() / 32); - $count = $this->getUnsignedVarInt(); - for($i = 0; $i < $count; ++$i){ - $x = $y = $z = null; - $this->getSignedBlockPosition($x, $y, $z); - $this->records[$i] = new Vector3($x, $y, $z); - } - } - - protected function encodePayload(){ - $this->putVector3($this->position); - $this->putVarInt((int) ($this->radius * 32)); - $this->putUnsignedVarInt(count($this->records)); - if(count($this->records) > 0){ - foreach($this->records as $record){ - $this->putSignedBlockPosition((int) $record->x, (int) $record->y, (int) $record->z); - } - } - } - - public function handle(NetworkSession $session) : bool{ - return $session->handleExplode($this); - } -} diff --git a/src/pocketmine/network/mcpe/protocol/InventoryTransactionPacket.php b/src/pocketmine/network/mcpe/protocol/InventoryTransactionPacket.php index cd6bddc97..db57e7c9b 100644 --- a/src/pocketmine/network/mcpe/protocol/InventoryTransactionPacket.php +++ b/src/pocketmine/network/mcpe/protocol/InventoryTransactionPacket.php @@ -26,6 +26,7 @@ namespace pocketmine\network\mcpe\protocol; #include use pocketmine\network\mcpe\NetworkSession; +use pocketmine\network\mcpe\protocol\types\ContainerIds; use pocketmine\network\mcpe\protocol\types\NetworkInventoryAction; use function count; @@ -74,7 +75,26 @@ class InventoryTransactionPacket extends DataPacket{ $this->transactionType = $this->getUnsignedVarInt(); for($i = 0, $count = $this->getUnsignedVarInt(); $i < $count; ++$i){ - $this->actions[] = (new NetworkInventoryAction())->read($this); + $this->actions[] = $action = (new NetworkInventoryAction())->read($this); + + if( + $action->sourceType === NetworkInventoryAction::SOURCE_CONTAINER and + $action->windowId === ContainerIds::UI and + $action->inventorySlot === 50 and + !$action->oldItem->equalsExact($action->newItem) + ){ + $this->isCraftingPart = true; + if(!$action->oldItem->isNull() and $action->newItem->isNull()){ + $this->isFinalCraftingPart = true; + } + }elseif( + $action->sourceType === NetworkInventoryAction::SOURCE_TODO and ( + $action->windowId === NetworkInventoryAction::SOURCE_TYPE_CRAFTING_RESULT or + $action->windowId === NetworkInventoryAction::SOURCE_TYPE_CRAFTING_USE_INGREDIENT + ) + ){ + $this->isCraftingPart = true; + } } $this->trData = new \stdClass(); diff --git a/src/pocketmine/network/mcpe/protocol/MoveActorDeltaPacket.php b/src/pocketmine/network/mcpe/protocol/MoveActorDeltaPacket.php index 21ee91309..c92265abd 100644 --- a/src/pocketmine/network/mcpe/protocol/MoveActorDeltaPacket.php +++ b/src/pocketmine/network/mcpe/protocol/MoveActorDeltaPacket.php @@ -70,7 +70,7 @@ class MoveActorDeltaPacket extends DataPacket{ protected function decodePayload(){ $this->entityRuntimeId = $this->getEntityRuntimeId(); - $this->flags = $this->getByte(); + $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); @@ -93,7 +93,7 @@ class MoveActorDeltaPacket extends DataPacket{ protected function encodePayload(){ $this->putEntityRuntimeId($this->entityRuntimeId); - $this->putByte($this->flags); + $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); diff --git a/src/pocketmine/network/mcpe/protocol/MultiplayerSettingsPacket.php b/src/pocketmine/network/mcpe/protocol/MultiplayerSettingsPacket.php new file mode 100644 index 000000000..b12afd01a --- /dev/null +++ b/src/pocketmine/network/mcpe/protocol/MultiplayerSettingsPacket.php @@ -0,0 +1,61 @@ + + +use pocketmine\network\mcpe\NetworkSession; + +class MultiplayerSettingsPacket extends DataPacket/* implements ServerboundPacket*/{ //TODO: this might be clientbound too, but unsure + public const NETWORK_ID = ProtocolInfo::MULTIPLAYER_SETTINGS_PACKET; + + public const ACTION_ENABLE_MULTIPLAYER = 0; + public const ACTION_DISABLE_MULTIPLAYER = 1; + public const ACTION_REFRESH_JOIN_CODE = 2; + + /** @var int */ + private $action; + + public static function create(int $action) : self{ + $result = new self; + $result->action = $action; + return $result; + } + + public function getAction() : int{ + return $this->action; + } + + protected function decodePayload() : void{ + $this->action = $this->getVarInt(); + } + + protected function encodePayload() : void{ + $this->putVarInt($this->action); + } + + public function handle(NetworkSession $handler) : bool{ + return $handler->handleMultiplayerSettings($this); + } +} diff --git a/src/pocketmine/network/mcpe/protocol/NetworkSettingsPacket.php b/src/pocketmine/network/mcpe/protocol/NetworkSettingsPacket.php new file mode 100644 index 000000000..f37292129 --- /dev/null +++ b/src/pocketmine/network/mcpe/protocol/NetworkSettingsPacket.php @@ -0,0 +1,60 @@ + + +use pocketmine\network\mcpe\NetworkSession; + +class NetworkSettingsPacket extends DataPacket/* implements ClientboundPacket*/{ + public const NETWORK_ID = ProtocolInfo::NETWORK_SETTINGS_PACKET; + + public const COMPRESS_NOTHING = 0; + public const COMPRESS_EVERYTHING = 1; + + /** @var int */ + private $compressionThreshold; + + public static function create(int $compressionThreshold) : self{ + $result = new self; + $result->compressionThreshold = $compressionThreshold; + return $result; + } + + public function getCompressionThreshold() : int{ + return $this->compressionThreshold; + } + + protected function decodePayload() : void{ + $this->compressionThreshold = $this->getLShort(); + } + + protected function encodePayload() : void{ + $this->putLShort($this->compressionThreshold); + } + + public function handle(NetworkSession $handler) : bool{ + return $handler->handleNetworkSettings($this); + } +} diff --git a/src/pocketmine/network/mcpe/protocol/PacketPool.php b/src/pocketmine/network/mcpe/protocol/PacketPool.php index 05c532d60..3ca054104 100644 --- a/src/pocketmine/network/mcpe/protocol/PacketPool.php +++ b/src/pocketmine/network/mcpe/protocol/PacketPool.php @@ -54,7 +54,7 @@ class PacketPool{ static::registerPacket(new RiderJumpPacket()); static::registerPacket(new UpdateBlockPacket()); static::registerPacket(new AddPaintingPacket()); - static::registerPacket(new ExplodePacket()); + static::registerPacket(new TickSyncPacket()); static::registerPacket(new LevelSoundEventPacketV1()); static::registerPacket(new LevelEventPacket()); static::registerPacket(new BlockEventPacket()); @@ -162,11 +162,19 @@ class PacketPool{ static::registerPacket(new ClientCacheStatusPacket()); static::registerPacket(new OnScreenTextureAnimationPacket()); static::registerPacket(new MapCreateLockedCopyPacket()); - static::registerPacket(new StructureTemplateDataExportRequestPacket()); - static::registerPacket(new StructureTemplateDataExportResponsePacket()); + static::registerPacket(new StructureTemplateDataRequestPacket()); + static::registerPacket(new StructureTemplateDataResponsePacket()); static::registerPacket(new UpdateBlockPropertiesPacket()); static::registerPacket(new ClientCacheBlobStatusPacket()); static::registerPacket(new ClientCacheMissResponsePacket()); + static::registerPacket(new EducationSettingsPacket()); + static::registerPacket(new EmotePacket()); + static::registerPacket(new MultiplayerSettingsPacket()); + static::registerPacket(new SettingsCommandPacket()); + static::registerPacket(new AnvilDamagePacket()); + static::registerPacket(new CompletedUsingItemPacket()); + static::registerPacket(new NetworkSettingsPacket()); + static::registerPacket(new PlayerAuthInputPacket()); } /** diff --git a/src/pocketmine/network/mcpe/protocol/PlayerAuthInputPacket.php b/src/pocketmine/network/mcpe/protocol/PlayerAuthInputPacket.php new file mode 100644 index 000000000..91cac5c16 --- /dev/null +++ b/src/pocketmine/network/mcpe/protocol/PlayerAuthInputPacket.php @@ -0,0 +1,175 @@ + + +use pocketmine\math\Vector3; +use pocketmine\network\mcpe\NetworkSession; +use pocketmine\network\mcpe\protocol\types\InputMode; +use pocketmine\network\mcpe\protocol\types\PlayMode; +use function assert; + +class PlayerAuthInputPacket extends DataPacket/* implements ServerboundPacket*/{ + public const NETWORK_ID = ProtocolInfo::PLAYER_AUTH_INPUT_PACKET; + + /** @var Vector3 */ + private $position; + /** @var float */ + private $pitch; + /** @var float */ + private $yaw; + /** @var float */ + private $headYaw; + /** @var float */ + private $moveVecX; + /** @var float */ + private $moveVecZ; + /** @var int */ + private $inputFlags; + /** @var int */ + private $inputMode; + /** @var int */ + private $playMode; + /** @var Vector3|null */ + private $vrGazeDirection = null; + + /** + * @param Vector3 $position + * @param float $pitch + * @param float $yaw + * @param float $headYaw + * @param float $moveVecX + * @param float $moveVecZ + * @param int $inputFlags + * @param int $inputMode @see InputMode + * @param int $playMode @see PlayMode + * @param Vector3|null $vrGazeDirection only used when PlayMode::VR + * + * @return 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 = null) : 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"); + } + $result = new self; + $result->position = $position->asVector3(); + $result->pitch = $pitch; + $result->yaw = $yaw; + $result->headYaw = $headYaw; + $result->moveVecX = $moveVecX; + $result->moveVecZ = $moveVecZ; + $result->inputFlags = $inputFlags; + $result->inputMode = $inputMode; + $result->playMode = $playMode; + if($vrGazeDirection !== null){ + $this->vrGazeDirection = $vrGazeDirection->asVector3(); + } + return $result; + } + + public function getPosition() : Vector3{ + return $this->position; + } + + public function getPitch() : float{ + return $this->pitch; + } + + public function getYaw() : float{ + return $this->yaw; + } + + public function getHeadYaw() : float{ + return $this->headYaw; + } + + public function getMoveVecX() : float{ + return $this->moveVecX; + } + + public function getMoveVecZ() : float{ + return $this->moveVecZ; + } + + public function getInputFlags() : int{ + return $this->inputFlags; + } + + /** + * @see InputMode + * @return int + */ + public function getInputMode() : int{ + return $this->inputMode; + } + + /** + * @see PlayMode + * @return int + */ + public function getPlayMode() : int{ + return $this->playMode; + } + + public function getVrGazeDirection() : ?Vector3{ + return $this->vrGazeDirection; + } + + protected function decodePayload() : void{ + $this->yaw = $this->getLFloat(); + $this->pitch = $this->getLFloat(); + $this->position = $this->getVector3(); + $this->moveVecX = $this->getLFloat(); + $this->moveVecZ = $this->getLFloat(); + $this->headYaw = $this->getLFloat(); + $this->inputFlags = $this->getUnsignedVarLong(); + $this->inputMode = $this->getUnsignedVarInt(); + $this->playMode = $this->getUnsignedVarInt(); + if($this->playMode === PlayMode::VR){ + $this->vrGazeDirection = $this->getVector3(); + } + } + + protected function encodePayload() : void{ + $this->putLFloat($this->yaw); + $this->putLFloat($this->pitch); + $this->putVector3($this->position); + $this->putLFloat($this->moveVecX); + $this->putLFloat($this->moveVecZ); + $this->putLFloat($this->headYaw); + $this->putUnsignedVarLong($this->inputFlags); + $this->putUnsignedVarInt($this->inputMode); + $this->putUnsignedVarInt($this->playMode); + if($this->playMode === PlayMode::VR){ + assert($this->vrGazeDirection !== null); + $this->putVector3($this->vrGazeDirection); + } + } + + public function handle(NetworkSession $handler) : bool{ + return $handler->handlePlayerAuthInput($this); + } +} diff --git a/src/pocketmine/network/mcpe/protocol/PlayerListPacket.php b/src/pocketmine/network/mcpe/protocol/PlayerListPacket.php index 9d8bd8f1b..f73abb254 100644 --- a/src/pocketmine/network/mcpe/protocol/PlayerListPacket.php +++ b/src/pocketmine/network/mcpe/protocol/PlayerListPacket.php @@ -26,8 +26,8 @@ namespace pocketmine\network\mcpe\protocol; #include -use pocketmine\entity\Skin; use pocketmine\network\mcpe\NetworkSession; +use pocketmine\network\mcpe\protocol\types\SkinData; use pocketmine\network\mcpe\protocol\types\PlayerListEntry; use function count; @@ -57,22 +57,12 @@ class PlayerListPacket extends DataPacket{ $entry->uuid = $this->getUUID(); $entry->entityUniqueId = $this->getEntityUniqueId(); $entry->username = $this->getString(); - - $skinId = $this->getString(); - $skinData = $this->getString(); - $capeData = $this->getString(); - $geometryName = $this->getString(); - $geometryData = $this->getString(); - - $entry->skin = new Skin( - $skinId, - $skinData, - $capeData, - $geometryName, - $geometryData - ); $entry->xboxUserId = $this->getString(); $entry->platformChatId = $this->getString(); + $entry->buildPlatform = $this->getLInt(); + $entry->skinData = $this->getSkin(); + $entry->isTeacher = $this->getBool(); + $entry->isHost = $this->getBool(); }else{ $entry->uuid = $this->getUUID(); } @@ -89,13 +79,12 @@ class PlayerListPacket extends DataPacket{ $this->putUUID($entry->uuid); $this->putEntityUniqueId($entry->entityUniqueId); $this->putString($entry->username); - $this->putString($entry->skin->getSkinId()); - $this->putString($entry->skin->getSkinData()); - $this->putString($entry->skin->getCapeData()); - $this->putString($entry->skin->getGeometryName()); - $this->putString($entry->skin->getGeometryData()); $this->putString($entry->xboxUserId); $this->putString($entry->platformChatId); + $this->putLInt($entry->buildPlatform); + $this->putSkin($entry->skinData); + $this->putBool($entry->isTeacher); + $this->putBool($entry->isHost); }else{ $this->putUUID($entry->uuid); } diff --git a/src/pocketmine/network/mcpe/protocol/PlayerSkinPacket.php b/src/pocketmine/network/mcpe/protocol/PlayerSkinPacket.php index ea56e12cb..f197386b4 100644 --- a/src/pocketmine/network/mcpe/protocol/PlayerSkinPacket.php +++ b/src/pocketmine/network/mcpe/protocol/PlayerSkinPacket.php @@ -25,8 +25,8 @@ namespace pocketmine\network\mcpe\protocol; #include -use pocketmine\entity\Skin; use pocketmine\network\mcpe\NetworkSession; +use pocketmine\network\mcpe\protocol\types\SkinData; use pocketmine\utils\UUID; class PlayerSkinPacket extends DataPacket{ @@ -38,39 +38,21 @@ class PlayerSkinPacket extends DataPacket{ public $oldSkinName = ""; /** @var string */ public $newSkinName = ""; - /** @var Skin */ + /** @var SkinData */ public $skin; - /** @var bool */ - public $premiumSkin = false; protected function decodePayload(){ $this->uuid = $this->getUUID(); - - $skinId = $this->getString(); + $this->skin = $this->getSkin(); $this->newSkinName = $this->getString(); $this->oldSkinName = $this->getString(); - $skinData = $this->getString(); - $capeData = $this->getString(); - $geometryModel = $this->getString(); - $geometryData = $this->getString(); - - $this->skin = new Skin($skinId, $skinData, $capeData, $geometryModel, $geometryData); - - $this->premiumSkin = $this->getBool(); } protected function encodePayload(){ $this->putUUID($this->uuid); - - $this->putString($this->skin->getSkinId()); + $this->putSkin($this->skin); $this->putString($this->newSkinName); $this->putString($this->oldSkinName); - $this->putString($this->skin->getSkinData()); - $this->putString($this->skin->getCapeData()); - $this->putString($this->skin->getGeometryName()); - $this->putString($this->skin->getGeometryData()); - - $this->putBool($this->premiumSkin); } public function handle(NetworkSession $session) : bool{ diff --git a/src/pocketmine/network/mcpe/protocol/ProtocolInfo.php b/src/pocketmine/network/mcpe/protocol/ProtocolInfo.php index 152dd4463..242fe0695 100644 --- a/src/pocketmine/network/mcpe/protocol/ProtocolInfo.php +++ b/src/pocketmine/network/mcpe/protocol/ProtocolInfo.php @@ -39,15 +39,15 @@ interface ProtocolInfo{ /** * Actual Minecraft: PE protocol version */ - public const CURRENT_PROTOCOL = 361; + public const CURRENT_PROTOCOL = 388; /** * Current Minecraft PE version reported by the server. This is usually the earliest currently supported version. */ - public const MINECRAFT_VERSION = 'v1.12.0'; + public const MINECRAFT_VERSION = 'v1.13.0'; /** * Version number sent to clients in ping responses. */ - public const MINECRAFT_VERSION_NETWORK = '1.12.0'; + public const MINECRAFT_VERSION_NETWORK = '1.13.0'; public const LOGIN_PACKET = 0x01; public const PLAY_STATUS_PACKET = 0x02; @@ -71,7 +71,7 @@ interface ProtocolInfo{ public const RIDER_JUMP_PACKET = 0x14; public const UPDATE_BLOCK_PACKET = 0x15; public const ADD_PAINTING_PACKET = 0x16; - public const EXPLODE_PACKET = 0x17; + public const TICK_SYNC_PACKET = 0x17; public const LEVEL_SOUND_EVENT_PACKET_V1 = 0x18; public const LEVEL_EVENT_PACKET = 0x19; public const BLOCK_EVENT_PACKET = 0x1a; @@ -180,10 +180,18 @@ interface ProtocolInfo{ public const CLIENT_CACHE_STATUS_PACKET = 0x81; public const ON_SCREEN_TEXTURE_ANIMATION_PACKET = 0x82; public const MAP_CREATE_LOCKED_COPY_PACKET = 0x83; - public const STRUCTURE_TEMPLATE_DATA_EXPORT_REQUEST_PACKET = 0x84; - public const STRUCTURE_TEMPLATE_DATA_EXPORT_RESPONSE_PACKET = 0x85; + 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; + public const EMOTE_PACKET = 0x8a; + public const MULTIPLAYER_SETTINGS_PACKET = 0x8b; + public const SETTINGS_COMMAND_PACKET = 0x8c; + public const ANVIL_DAMAGE_PACKET = 0x8d; + public const COMPLETED_USING_ITEM_PACKET = 0x8e; + public const NETWORK_SETTINGS_PACKET = 0x8f; + public const PLAYER_AUTH_INPUT_PACKET = 0x90; } diff --git a/src/pocketmine/network/mcpe/protocol/ResourcePackChunkDataPacket.php b/src/pocketmine/network/mcpe/protocol/ResourcePackChunkDataPacket.php index 93b86e810..07d34affd 100644 --- a/src/pocketmine/network/mcpe/protocol/ResourcePackChunkDataPacket.php +++ b/src/pocketmine/network/mcpe/protocol/ResourcePackChunkDataPacket.php @@ -46,15 +46,14 @@ class ResourcePackChunkDataPacket extends DataPacket{ $this->packId = $this->getString(); $this->chunkIndex = $this->getLInt(); $this->progress = $this->getLLong(); - $this->data = $this->get($this->getLInt()); + $this->data = $this->getString(); } protected function encodePayload(){ $this->putString($this->packId); $this->putLInt($this->chunkIndex); $this->putLLong($this->progress); - $this->putLInt(strlen($this->data)); - $this->put($this->data); + $this->putString($this->data); } public function handle(NetworkSession $session) : bool{ diff --git a/src/pocketmine/network/mcpe/protocol/ResourcePackStackPacket.php b/src/pocketmine/network/mcpe/protocol/ResourcePackStackPacket.php index 8ec61a229..41fd445e7 100644 --- a/src/pocketmine/network/mcpe/protocol/ResourcePackStackPacket.php +++ b/src/pocketmine/network/mcpe/protocol/ResourcePackStackPacket.php @@ -44,6 +44,8 @@ class ResourcePackStackPacket extends DataPacket{ /** @var bool */ public $isExperimental = false; + /** @var string */ + public $baseGameVersion = ProtocolInfo::MINECRAFT_VERSION_NETWORK; protected function decodePayload(){ $this->mustAccept = $this->getBool(); @@ -62,6 +64,7 @@ class ResourcePackStackPacket extends DataPacket{ } $this->isExperimental = $this->getBool(); + $this->baseGameVersion = $this->getString(); } protected function encodePayload(){ @@ -82,6 +85,7 @@ class ResourcePackStackPacket extends DataPacket{ } $this->putBool($this->isExperimental); + $this->putString($this->baseGameVersion); } public function handle(NetworkSession $session) : bool{ diff --git a/src/pocketmine/network/mcpe/protocol/RespawnPacket.php b/src/pocketmine/network/mcpe/protocol/RespawnPacket.php index b13e7bcb7..6705877e8 100644 --- a/src/pocketmine/network/mcpe/protocol/RespawnPacket.php +++ b/src/pocketmine/network/mcpe/protocol/RespawnPacket.php @@ -32,15 +32,27 @@ use pocketmine\network\mcpe\NetworkSession; class RespawnPacket extends DataPacket{ public const NETWORK_ID = ProtocolInfo::RESPAWN_PACKET; + public const SEARCHING_FOR_SPAWN = 0; + public const READY_TO_SPAWN = 1; + public const CLIENT_READY_TO_SPAWN = 2; + /** @var Vector3 */ public $position; + /** @var int */ + public $respawnState = self::SEARCHING_FOR_SPAWN; + /** @var int */ + public $entityRuntimeId; protected function decodePayload(){ $this->position = $this->getVector3(); + $this->respawnState = $this->getByte(); + $this->entityRuntimeId = $this->getEntityRuntimeId(); } protected function encodePayload(){ $this->putVector3($this->position); + $this->putByte($this->respawnState); + $this->putEntityRuntimeId($this->entityRuntimeId); } public function handle(NetworkSession $session) : bool{ diff --git a/src/pocketmine/network/mcpe/protocol/SettingsCommandPacket.php b/src/pocketmine/network/mcpe/protocol/SettingsCommandPacket.php new file mode 100644 index 000000000..d0ac8f5f5 --- /dev/null +++ b/src/pocketmine/network/mcpe/protocol/SettingsCommandPacket.php @@ -0,0 +1,66 @@ + + +use pocketmine\network\mcpe\NetworkSession; + +class SettingsCommandPacket extends DataPacket/* implements ServerboundPacket*/{ + public const NETWORK_ID = ProtocolInfo::SETTINGS_COMMAND_PACKET; + + /** @var string */ + private $command; + /** @var bool */ + private $suppressOutput; + + public static function create(string $command, bool $suppressOutput) : self{ + $result = new self; + $result->command = $command; + $result->suppressOutput = $suppressOutput; + return $result; + } + + public function getCommand() : string{ + return $this->command; + } + + public function getSuppressOutput() : bool{ + return $this->suppressOutput; + } + + protected function decodePayload() : void{ + $this->command = $this->getString(); + $this->suppressOutput = $this->getBool(); + } + + protected function encodePayload() : void{ + $this->putString($this->command); + $this->putBool($this->suppressOutput); + } + + public function handle(NetworkSession $handler) : bool{ + return $handler->handleSettingsCommand($this); + } +} diff --git a/src/pocketmine/network/mcpe/protocol/StartGamePacket.php b/src/pocketmine/network/mcpe/protocol/StartGamePacket.php index 557dee7ca..8d3611c8f 100644 --- a/src/pocketmine/network/mcpe/protocol/StartGamePacket.php +++ b/src/pocketmine/network/mcpe/protocol/StartGamePacket.php @@ -27,6 +27,10 @@ namespace pocketmine\network\mcpe\protocol; use pocketmine\math\Vector3; +use pocketmine\nbt\NetworkLittleEndianNBTStream; +use pocketmine\nbt\tag\CompoundTag; +use pocketmine\nbt\tag\ListTag; +use pocketmine\nbt\tag\StringTag; use pocketmine\network\mcpe\NetworkBinaryStream; use pocketmine\network\mcpe\NetworkSession; use pocketmine\network\mcpe\protocol\types\PlayerPermissions; @@ -79,8 +83,8 @@ class StartGamePacket extends DataPacket{ public $hasAchievementsDisabled = true; /** @var int */ public $time = -1; - /** @var bool */ - public $eduMode = false; + /** @var int */ + public $eduEditionOffer = 0; /** @var bool */ public $hasEduFeaturesEnabled = false; /** @var float */ @@ -130,6 +134,8 @@ class StartGamePacket extends DataPacket{ /** @var bool */ public $onlySpawnV1Villagers = false; + /** @var string */ + public $vanillaVersion = ProtocolInfo::MINECRAFT_VERSION_NETWORK; /** @var string */ public $levelId = ""; //base64 string, usually the same as world folder name in vanilla /** @var string */ @@ -138,6 +144,8 @@ class StartGamePacket extends DataPacket{ public $premiumWorldTemplateId = ""; /** @var bool */ public $isTrial = false; + /** @var bool */ + public $isMovementServerAuthoritative = false; /** @var int */ public $currentTick = 0; //only used if isTrial is true /** @var int */ @@ -145,7 +153,7 @@ class StartGamePacket extends DataPacket{ /** @var string */ public $multiplayerCorrelationId = ""; //TODO: this should be filled with a UUID of some sort - /** @var array|null ["name" (string), "data" (int16), "legacy_id" (int16)] */ + /** @var ListTag|null */ public $blockTable = null; /** @var array|null string (name) => int16 (legacyID) */ public $itemTable = null; @@ -169,7 +177,7 @@ class StartGamePacket extends DataPacket{ $this->getBlockPosition($this->spawnX, $this->spawnY, $this->spawnZ); $this->hasAchievementsDisabled = $this->getBool(); $this->time = $this->getVarInt(); - $this->eduMode = $this->getBool(); + $this->eduEditionOffer = $this->getVarInt(); $this->hasEduFeaturesEnabled = $this->getBool(); $this->rainLevel = $this->getLFloat(); $this->lightningLevel = $this->getLFloat(); @@ -193,22 +201,22 @@ class StartGamePacket extends DataPacket{ $this->isWorldTemplateOptionLocked = $this->getBool(); $this->onlySpawnV1Villagers = $this->getBool(); + $this->vanillaVersion = $this->getString(); $this->levelId = $this->getString(); $this->worldName = $this->getString(); $this->premiumWorldTemplateId = $this->getString(); $this->isTrial = $this->getBool(); + $this->isMovementServerAuthoritative = $this->getBool(); $this->currentTick = $this->getLLong(); $this->enchantmentSeed = $this->getVarInt(); - $this->blockTable = []; - for($i = 0, $count = $this->getUnsignedVarInt(); $i < $count; ++$i){ - $id = $this->getString(); - $data = $this->getSignedLShort(); - $unknown = $this->getSignedLShort(); - - $this->blockTable[$i] = ["name" => $id, "data" => $data, "legacy_id" => $unknown]; + $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->blockTable = $blockTable; + $this->itemTable = []; for($i = 0, $count = $this->getUnsignedVarInt(); $i < $count; ++$i){ $id = $this->getString(); @@ -239,7 +247,7 @@ class StartGamePacket extends DataPacket{ $this->putBlockPosition($this->spawnX, $this->spawnY, $this->spawnZ); $this->putBool($this->hasAchievementsDisabled); $this->putVarInt($this->time); - $this->putBool($this->eduMode); + $this->putVarInt($this->eduEditionOffer); $this->putBool($this->hasEduFeaturesEnabled); $this->putLFloat($this->rainLevel); $this->putLFloat($this->lightningLevel); @@ -263,10 +271,12 @@ class StartGamePacket extends DataPacket{ $this->putBool($this->isWorldTemplateOptionLocked); $this->putBool($this->onlySpawnV1Villagers); + $this->putString($this->vanillaVersion); $this->putString($this->levelId); $this->putString($this->worldName); $this->putString($this->premiumWorldTemplateId); $this->putBool($this->isTrial); + $this->putBool($this->isMovementServerAuthoritative); $this->putLLong($this->currentTick); $this->putVarInt($this->enchantmentSeed); @@ -274,11 +284,11 @@ class StartGamePacket extends DataPacket{ if($this->blockTable === null){ if(self::$blockTableCache === null){ //this is a really nasty hack, but it'll do for now - self::$blockTableCache = self::serializeBlockTable(RuntimeBlockMapping::getBedrockKnownStates()); + self::$blockTableCache = (new NetworkLittleEndianNBTStream())->write(new ListTag("", RuntimeBlockMapping::getBedrockKnownStates())); } $this->put(self::$blockTableCache); }else{ - $this->put(self::serializeBlockTable($this->blockTable)); + $this->put((new NetworkLittleEndianNBTStream())->write($this->blockTable)); } if($this->itemTable === null){ if(self::$itemTableCache === null){ @@ -292,17 +302,6 @@ class StartGamePacket extends DataPacket{ $this->putString($this->multiplayerCorrelationId); } - private static function serializeBlockTable(array $table) : string{ - $stream = new NetworkBinaryStream(); - $stream->putUnsignedVarInt(count($table)); - foreach($table as $v){ - $stream->putString($v["name"]); - $stream->putLShort($v["data"]); - $stream->putLShort($v["legacy_id"]); - } - return $stream->getBuffer(); - } - private static function serializeItemTable(array $table) : string{ $stream = new NetworkBinaryStream(); $stream->putUnsignedVarInt(count($table)); diff --git a/src/pocketmine/network/mcpe/protocol/StructureTemplateDataExportRequestPacket.php b/src/pocketmine/network/mcpe/protocol/StructureTemplateDataRequestPacket.php similarity index 92% rename from src/pocketmine/network/mcpe/protocol/StructureTemplateDataExportRequestPacket.php rename to src/pocketmine/network/mcpe/protocol/StructureTemplateDataRequestPacket.php index 6ae16cec4..ba100bb60 100644 --- a/src/pocketmine/network/mcpe/protocol/StructureTemplateDataExportRequestPacket.php +++ b/src/pocketmine/network/mcpe/protocol/StructureTemplateDataRequestPacket.php @@ -28,8 +28,8 @@ namespace pocketmine\network\mcpe\protocol; use pocketmine\network\mcpe\NetworkSession; use pocketmine\network\mcpe\protocol\types\StructureSettings; -class StructureTemplateDataExportRequestPacket extends DataPacket{ - public const NETWORK_ID = ProtocolInfo::STRUCTURE_TEMPLATE_DATA_EXPORT_REQUEST_PACKET; +class StructureTemplateDataRequestPacket extends DataPacket{ + public const NETWORK_ID = ProtocolInfo::STRUCTURE_TEMPLATE_DATA_REQUEST_PACKET; public const TYPE_ALWAYS_LOAD = 1; public const TYPE_CREATE_AND_LOAD = 2; @@ -62,6 +62,6 @@ class StructureTemplateDataExportRequestPacket extends DataPacket{ } public function handle(NetworkSession $handler) : bool{ - return $handler->handleStructureTemplateDataExportRequest($this); + return $handler->handleStructureTemplateDataRequest($this); } } diff --git a/src/pocketmine/network/mcpe/protocol/StructureTemplateDataExportResponsePacket.php b/src/pocketmine/network/mcpe/protocol/StructureTemplateDataResponsePacket.php similarity index 90% rename from src/pocketmine/network/mcpe/protocol/StructureTemplateDataExportResponsePacket.php rename to src/pocketmine/network/mcpe/protocol/StructureTemplateDataResponsePacket.php index 2ff52b116..98afaaed7 100644 --- a/src/pocketmine/network/mcpe/protocol/StructureTemplateDataExportResponsePacket.php +++ b/src/pocketmine/network/mcpe/protocol/StructureTemplateDataResponsePacket.php @@ -27,8 +27,8 @@ namespace pocketmine\network\mcpe\protocol; use pocketmine\network\mcpe\NetworkSession; -class StructureTemplateDataExportResponsePacket extends DataPacket{ - public const NETWORK_ID = ProtocolInfo::STRUCTURE_TEMPLATE_DATA_EXPORT_RESPONSE_PACKET; +class StructureTemplateDataResponsePacket extends DataPacket{ + public const NETWORK_ID = ProtocolInfo::STRUCTURE_TEMPLATE_DATA_RESPONSE_PACKET; /** @var string */ public $structureTemplateName; @@ -51,6 +51,6 @@ class StructureTemplateDataExportResponsePacket extends DataPacket{ } public function handle(NetworkSession $handler) : bool{ - return $handler->handleStructureTemplateDataExportResponse($this); + return $handler->handleStructureTemplateDataResponse($this); } } diff --git a/src/pocketmine/network/mcpe/protocol/TickSyncPacket.php b/src/pocketmine/network/mcpe/protocol/TickSyncPacket.php new file mode 100644 index 000000000..4c14fc8aa --- /dev/null +++ b/src/pocketmine/network/mcpe/protocol/TickSyncPacket.php @@ -0,0 +1,73 @@ + + +use pocketmine\network\mcpe\NetworkSession; + +class TickSyncPacket extends DataPacket/* implements ClientboundPacket, ServerboundPacket*/{ + public const NETWORK_ID = ProtocolInfo::TICK_SYNC_PACKET; + + /** @var int */ + private $clientSendTime; + /** @var int */ + private $serverReceiveTime; + + public static function request(int $clientTime) : self{ + $result = new self; + $result->clientSendTime = $clientTime; + $result->serverReceiveTime = 0; //useless + return $result; + } + + public static function response(int $clientSendTime, int $serverReceiveTime) : self{ + $result = new self; + $result->clientSendTime = $clientSendTime; + $result->serverReceiveTime = $serverReceiveTime; + return $result; + } + + public function getClientSendTime() : int{ + return $this->clientSendTime; + } + + public function getServerReceiveTime() : int{ + return $this->serverReceiveTime; + } + + protected function decodePayload() : void{ + $this->clientSendTime = $this->getLLong(); + $this->serverReceiveTime = $this->getLLong(); + } + + protected function encodePayload() : void{ + $this->putLLong($this->clientSendTime); + $this->putLLong($this->serverReceiveTime); + } + + public function handle(NetworkSession $handler) : bool{ + return $handler->handleTickSync($this); + } +} diff --git a/src/pocketmine/network/mcpe/protocol/types/CommandEnumConstraint.php b/src/pocketmine/network/mcpe/protocol/types/CommandEnumConstraint.php new file mode 100644 index 000000000..a97bb9cda --- /dev/null +++ b/src/pocketmine/network/mcpe/protocol/types/CommandEnumConstraint.php @@ -0,0 +1,67 @@ +enumValues[$valueOffset])){ + throw new \InvalidArgumentException("Invalid enum value offset $valueOffset"); + } + $this->enum = $enum; + $this->valueOffset = $valueOffset; + $this->constraints = $constraints; + } + + public function getEnum() : CommandEnum{ + return $this->enum; + } + + public function getValueOffset() : int{ + return $this->valueOffset; + } + + public function getAffectedValue() : string{ + return $this->enum->enumValues[$this->valueOffset]; + } + + /** + * @return int[] + */ + public function getConstraints() : array{ + return $this->constraints; + } +} diff --git a/src/pocketmine/network/mcpe/protocol/types/CommandParameter.php b/src/pocketmine/network/mcpe/protocol/types/CommandParameter.php index b1a00137b..a2f1a26ed 100644 --- a/src/pocketmine/network/mcpe/protocol/types/CommandParameter.php +++ b/src/pocketmine/network/mcpe/protocol/types/CommandParameter.php @@ -24,6 +24,9 @@ declare(strict_types=1); namespace pocketmine\network\mcpe\protocol\types; class CommandParameter{ + public const FLAG_FORCE_COLLAPSE_ENUM = 0x1; + public const FLAG_HAS_ENUM_CONSTRAINT = 0x2; + /** @var string */ public $paramName; /** @var int */ diff --git a/src/pocketmine/network/mcpe/protocol/types/ContainerIds.php b/src/pocketmine/network/mcpe/protocol/types/ContainerIds.php index 3b55065e5..0c9769f9f 100644 --- a/src/pocketmine/network/mcpe/protocol/types/ContainerIds.php +++ b/src/pocketmine/network/mcpe/protocol/types/ContainerIds.php @@ -34,6 +34,6 @@ interface ContainerIds{ public const CREATIVE = 121; public const HOTBAR = 122; public const FIXED_INVENTORY = 123; - public const CURSOR = 124; + public const UI = 124; } diff --git a/src/pocketmine/network/mcpe/protocol/types/InputMode.php b/src/pocketmine/network/mcpe/protocol/types/InputMode.php new file mode 100644 index 000000000..48b1dccb7 --- /dev/null +++ b/src/pocketmine/network/mcpe/protocol/types/InputMode.php @@ -0,0 +1,36 @@ +getCapeData()); + if($skin->getCapeData() === ""){ + $capeData = new SkinImage(0, 0, $skin->getCapeData()); + } + return new SkinData( + $skin->getSkinId(), + json_encode(["geometry" => ["default" => $skin->getGeometryName()]]), + SkinImage::fromLegacy($skin->getSkinData()), [], + $capeData, + $skin->getGeometryData() + ); + } + + public function fromSkinData(SkinData $data) : Skin{ + $capeData = $data->getCapeImage()->getData(); + $geometryName = ""; + $resourcePatch = json_decode($data->getResourcePatch(), true); + if(is_array($resourcePatch["geometry"]) && is_string($resourcePatch["geometry"]["default"])){ + $geometryName = $resourcePatch["geometry"]["default"]; + }else{ + //TODO: Kick for invalid skin + } + if($data->isPersona()){ + return new Skin("Standard_Custom", str_repeat(random_bytes(3) . "\xff", 2048), "", "geometry.humanoid.custom"); + }elseif($data->isPersonaCapeOnClassic()){ + $capeData = ""; + } + return new Skin($data->getSkinId(), $data->getSkinImage()->getData(), $capeData, $geometryName, $data->getGeometryData()); + } +} \ No newline at end of file diff --git a/src/pocketmine/network/mcpe/protocol/types/NetworkInventoryAction.php b/src/pocketmine/network/mcpe/protocol/types/NetworkInventoryAction.php index abe15d8b8..54ca69682 100644 --- a/src/pocketmine/network/mcpe/protocol/types/NetworkInventoryAction.php +++ b/src/pocketmine/network/mcpe/protocol/types/NetworkInventoryAction.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\network\mcpe\protocol\types; +use pocketmine\inventory\CraftingGrid; use pocketmine\inventory\transaction\action\CreativeInventoryAction; use pocketmine\inventory\transaction\action\DropItemAction; use pocketmine\inventory\transaction\action\InventoryAction; @@ -36,7 +37,6 @@ class NetworkInventoryAction{ public const SOURCE_WORLD = 2; //drop/pickup item entity public const SOURCE_CREATIVE = 3; - public const SOURCE_CRAFTING_GRID = 100; public const SOURCE_TODO = 99999; /** @@ -108,17 +108,8 @@ class NetworkInventoryAction{ break; case self::SOURCE_CREATIVE: break; - case self::SOURCE_CRAFTING_GRID: case self::SOURCE_TODO: $this->windowId = $packet->getVarInt(); - switch($this->windowId){ - /** @noinspection PhpMissingBreakStatementInspection */ - case self::SOURCE_TYPE_CRAFTING_RESULT: - $packet->isFinalCraftingPart = true; - case self::SOURCE_TYPE_CRAFTING_USE_INGREDIENT: - $packet->isCraftingPart = true; - break; - } break; default: throw new \UnexpectedValueException("Unknown inventory action source type $this->sourceType"); @@ -146,7 +137,6 @@ class NetworkInventoryAction{ break; case self::SOURCE_CREATIVE: break; - case self::SOURCE_CRAFTING_GRID: case self::SOURCE_TODO: $packet->putVarInt($this->windowId); break; @@ -167,11 +157,37 @@ class NetworkInventoryAction{ * @throws \UnexpectedValueException */ public function createInventoryAction(Player $player){ + if($this->oldItem->equalsExact($this->newItem)){ + //filter out useless noise in 1.13 + return null; + } switch($this->sourceType){ case self::SOURCE_CONTAINER: - $window = $player->getWindow($this->windowId); + if($this->windowId === ContainerIds::UI and $this->inventorySlot > 0){ + if($this->inventorySlot === 50){ + return null; //useless noise + } + if($this->inventorySlot >= 28 and $this->inventorySlot <= 31){ + $window = $player->getCraftingGrid(); + if($window->getGridWidth() !== CraftingGrid::SIZE_SMALL){ + throw new \UnexpectedValueException("Expected small crafting grid"); + } + $slot = $this->inventorySlot - 28; + }elseif($this->inventorySlot >= 32 and $this->inventorySlot <= 40){ + $window = $player->getCraftingGrid(); + if($window->getGridWidth() !== CraftingGrid::SIZE_BIG){ + throw new \UnexpectedValueException("Expected big crafting grid"); + } + $slot = $this->inventorySlot - 32; + }else{ + throw new \UnexpectedValueException("Unhandled magic UI slot offset $this->inventorySlot"); + } + }else{ + $window = $player->getWindow($this->windowId); + $slot = $this->inventorySlot; + } if($window !== null){ - return new SlotChangeAction($window, $this->inventorySlot, $this->oldItem, $this->newItem); + return new SlotChangeAction($window, $slot, $this->oldItem, $this->newItem); } throw new \UnexpectedValueException("Player " . $player->getName() . " has no open container with window ID $this->windowId"); @@ -195,7 +211,6 @@ class NetworkInventoryAction{ } return new CreativeInventoryAction($this->oldItem, $this->newItem, $type); - case self::SOURCE_CRAFTING_GRID: case self::SOURCE_TODO: //These types need special handling. switch($this->windowId){ diff --git a/src/pocketmine/network/mcpe/protocol/types/PlayMode.php b/src/pocketmine/network/mcpe/protocol/types/PlayMode.php new file mode 100644 index 000000000..37ccc066d --- /dev/null +++ b/src/pocketmine/network/mcpe/protocol/types/PlayMode.php @@ -0,0 +1,45 @@ +uuid = $uuid; $entry->entityUniqueId = $entityUniqueId; $entry->username = $username; - $entry->skin = $skin; + $entry->skinData = $skinData; $entry->xboxUserId = $xboxUserId; $entry->platformChatId = $platformChatId; + $entry->buildPlatform = $buildPlatform; + $entry->isTeacher = $isTeacher; + $entry->isHost = $isHost; return $entry; } diff --git a/src/pocketmine/network/mcpe/protocol/types/PotionContainerChangeRecipe.php b/src/pocketmine/network/mcpe/protocol/types/PotionContainerChangeRecipe.php new file mode 100644 index 000000000..8d3ad196e --- /dev/null +++ b/src/pocketmine/network/mcpe/protocol/types/PotionContainerChangeRecipe.php @@ -0,0 +1,51 @@ +inputItemId = $inputItemId; + $this->ingredientItemId = $ingredientItemId; + $this->outputItemId = $outputItemId; + } + + public function getInputItemId() : int{ + return $this->inputItemId; + } + + public function getIngredientItemId() : int{ + return $this->ingredientItemId; + } + + public function getOutputItemId() : int{ + return $this->outputItemId; + } +} \ No newline at end of file diff --git a/src/pocketmine/network/mcpe/protocol/types/PotionTypeRecipe.php b/src/pocketmine/network/mcpe/protocol/types/PotionTypeRecipe.php new file mode 100644 index 000000000..faacb6bf1 --- /dev/null +++ b/src/pocketmine/network/mcpe/protocol/types/PotionTypeRecipe.php @@ -0,0 +1,51 @@ +inputPotionType = $inputPotionType; + $this->ingredientItemId = $ingredientItemId; + $this->outputPotionType = $outputPotionType; + } + + public function getInputPotionType() : int{ + return $this->inputPotionType; + } + + public function getIngredientItemId() : int{ + return $this->ingredientItemId; + } + + public function getOutputPotionType() : int{ + return $this->outputPotionType; + } +} \ No newline at end of file diff --git a/src/pocketmine/network/mcpe/protocol/types/ResourcePackType.php b/src/pocketmine/network/mcpe/protocol/types/ResourcePackType.php index 9efaced08..bc4a217cf 100644 --- a/src/pocketmine/network/mcpe/protocol/types/ResourcePackType.php +++ b/src/pocketmine/network/mcpe/protocol/types/ResourcePackType.php @@ -30,9 +30,12 @@ final class ResourcePackType{ } public const INVALID = 0; - public const RESOURCES = 1; - public const BEHAVIORS = 2; - public const WORLD_TEMPLATE = 3; - public const ADDON = 4; //scripts? - public const SKINS = 5; + public const ADDON = 1; + public const CACHED = 2; + public const COPY_PROTECTED = 3; + public const BEHAVIORS = 4; + public const PERSONA_PIECE = 5; + public const RESOURCES = 6; + public const SKINS = 7; + public const WORLD_TEMPLATE = 8; } diff --git a/src/pocketmine/network/mcpe/protocol/types/RuntimeBlockMapping.php b/src/pocketmine/network/mcpe/protocol/types/RuntimeBlockMapping.php index 606148d1b..5fedc28b6 100644 --- a/src/pocketmine/network/mcpe/protocol/types/RuntimeBlockMapping.php +++ b/src/pocketmine/network/mcpe/protocol/types/RuntimeBlockMapping.php @@ -24,6 +24,12 @@ declare(strict_types=1); namespace pocketmine\network\mcpe\protocol\types; use pocketmine\block\BlockIds; +use pocketmine\nbt\NBT; +use pocketmine\nbt\NetworkLittleEndianNBTStream; +use pocketmine\nbt\tag\CompoundTag; +use pocketmine\nbt\tag\ListTag; +use pocketmine\nbt\tag\StringTag; +use pocketmine\utils\BinaryDataException; use function file_get_contents; use function getmypid; use function json_decode; @@ -40,7 +46,7 @@ final class RuntimeBlockMapping{ private static $legacyToRuntimeMap = []; /** @var int[] */ private static $runtimeToLegacyMap = []; - /** @var mixed[]|null */ + /** @var CompoundTag[]|null */ private static $bedrockKnownStates = null; private function __construct(){ @@ -48,32 +54,57 @@ final class RuntimeBlockMapping{ } public static function init() : void{ - $legacyIdMap = json_decode(file_get_contents(\pocketmine\RESOURCE_PATH . "vanilla/block_id_map.json"), true); - - $compressedTable = json_decode(file_get_contents(\pocketmine\RESOURCE_PATH . "vanilla/required_block_states.json"), true); - $decompressed = []; - - foreach($compressedTable as $prefix => $entries){ - foreach($entries as $shortStringId => $states){ - foreach($states as $state){ - $name = "$prefix:$shortStringId"; - $decompressed[] = [ - "name" => $name, - "data" => $state, - "legacy_id" => $legacyIdMap[$name] - ]; - } - } + $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"); } - self::$bedrockKnownStates = self::randomizeTable($decompressed); - foreach(self::$bedrockKnownStates as $k => $obj){ - if($obj["data"] > 15){ - //TODO: in 1.12 they started using data values bigger than 4 bits which we can't handle right now + /** @var CompoundTag[] $list */ + $list = $tag->getValue(); + self::$bedrockKnownStates = self::randomizeTable($list); + + self::setupLegacyMappings(); + } + + private static function setupLegacyMappings() : void{ + $legacyIdMap = json_decode(file_get_contents(\pocketmine\RESOURCE_PATH . "vanilla/block_id_map.json"), true); + $legacyStateMap = (new NetworkLittleEndianNBTStream())->read(file_get_contents(\pocketmine\RESOURCE_PATH . "vanilla/r12_to_current_block_map.nbt")); + if(!($legacyStateMap instanceof ListTag) or $legacyStateMap->getTagType() !== NBT::TAG_Compound){ + throw new \RuntimeException("Invalid legacy states mapping table, expected TAG_List root"); + } + + /** + * @var int[][] $idToStatesMap string id -> int[] list of candidate state indices + */ + $idToStatesMap = []; + foreach(self::$bedrockKnownStates as $k => $state){ + $idToStatesMap[$state->getCompoundTag("block")->getString("name")][] = $k; + } + /** @var CompoundTag $pair */ + foreach($legacyStateMap as $pair){ + $oldState = $pair->getCompoundTag("old"); + $id = $legacyIdMap[$oldState->getString("name")]; + $data = $oldState->getShort("val"); + if($data > 15){ + //we can't handle metadata with more than 4 bits continue; } - //this has to use the json offset to make sure the mapping is consistent with what we send over network, even though we aren't using all the entries - self::registerMapping($k, $obj["legacy_id"], $obj["data"]); + $mappedState = $pair->getCompoundTag("new"); + + //TODO HACK: idiotic NBT compare behaviour on 3.x compares keys which are stored by values + $mappedState->setName("block"); + $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"))){ + self::registerMapping($k, $id, $data); + continue 2; + } + } + throw new \RuntimeException("Mapped new state does not appear in network table"); } } @@ -88,9 +119,9 @@ final class RuntimeBlockMapping{ * 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 array $table + * @param CompoundTag[] $table * - * @return array + * @return CompoundTag[] */ private static function randomizeTable(array $table) : array{ $postSeed = mt_rand(); //save a seed to set afterwards, to avoid poor quality randoms @@ -133,7 +164,7 @@ final class RuntimeBlockMapping{ } /** - * @return array + * @return CompoundTag[] */ public static function getBedrockKnownStates() : array{ self::lazyInit(); diff --git a/src/pocketmine/network/mcpe/protocol/types/SkinAdapter.php b/src/pocketmine/network/mcpe/protocol/types/SkinAdapter.php new file mode 100644 index 000000000..56b940063 --- /dev/null +++ b/src/pocketmine/network/mcpe/protocol/types/SkinAdapter.php @@ -0,0 +1,48 @@ +image = $image; + $this->type = $type; + $this->frames = $frames; + } + + /** + * Image of the animation. + * + * @return SkinImage + */ + public function getImage() : SkinImage{ + return $this->image; + } + + /** + * The type of animation you are applying. + * + * @return int + */ + public function getType() : int{ + return $this->type; + } + + /** + * The total amount of frames in an animation. + * + * @return float + */ + public function getFrames() : float{ + return $this->frames; + } +} diff --git a/src/pocketmine/network/mcpe/protocol/types/SkinData.php b/src/pocketmine/network/mcpe/protocol/types/SkinData.php new file mode 100644 index 000000000..025595a92 --- /dev/null +++ b/src/pocketmine/network/mcpe/protocol/types/SkinData.php @@ -0,0 +1,155 @@ +skinId = $skinId; + $this->resourcePatch = $resourcePatch; + $this->skinImage = $skinImage; + $this->animations = $animations; + $this->capeImage = $capeImage; + $this->geometryData = $geometryData; + $this->animationData = $animationData; + $this->premium = $premium; + $this->persona = $persona; + $this->personaCapeOnClassic = $personaCapeOnClassic; + $this->capeId = $capeId; + } + + /** + * @return string + */ + public function getSkinId() : string{ + return $this->skinId; + } + + /** + * @return string + */ + public function getResourcePatch() : string{ + return $this->resourcePatch; + } + + /** + * @return SkinImage + */ + public function getSkinImage() : SkinImage{ + return $this->skinImage; + } + + /** + * @return SkinAnimation[] + */ + public function getAnimations() : array{ + return $this->animations; + } + + /** + * @return SkinImage + */ + public function getCapeImage() : SkinImage{ + return $this->capeImage; + } + + /** + * @return string + */ + public function getGeometryData() : string{ + return $this->geometryData; + } + + /** + * @return string + */ + public function getAnimationData() : string{ + return $this->animationData; + } + + /** + * @return bool + */ + public function isPersona() : bool{ + return $this->persona; + } + + /** + * @return bool + */ + public function isPremium() : bool{ + return $this->premium; + } + + /** + * @return bool + */ + public function isPersonaCapeOnClassic() : bool{ + return $this->personaCapeOnClassic; + } + + /** + * @return string + */ + public function getCapeId() : string{ + return $this->capeId; + } + +} \ No newline at end of file diff --git a/src/pocketmine/network/mcpe/protocol/types/SkinImage.php b/src/pocketmine/network/mcpe/protocol/types/SkinImage.php new file mode 100644 index 000000000..cbd7ebc16 --- /dev/null +++ b/src/pocketmine/network/mcpe/protocol/types/SkinImage.php @@ -0,0 +1,67 @@ +height = $height; + $this->width = $width; + $this->data = $data; + } + + public static function fromLegacy(string $data) : SkinImage{ + switch(strlen($data)){ + case 64 * 32 * 4: + return new self(64, 32, $data); + case 64 * 64 * 4: + return new self(64, 64, $data); + case 128 * 64 * 4: + return new self(128, 64, $data); + case 128 * 128 * 4: + return new self(128, 128, $data); + } + + throw new \InvalidArgumentException("Unknown size"); + } + + public function getHeight() : int{ + return $this->height; + } + + public function getWidth() : int{ + return $this->width; + } + + public function getData() : string{ + return $this->data; + } +} \ No newline at end of file diff --git a/src/pocketmine/resources/vanilla b/src/pocketmine/resources/vanilla index b5a8c68c4..a38b42788 160000 --- a/src/pocketmine/resources/vanilla +++ b/src/pocketmine/resources/vanilla @@ -1 +1 @@ -Subproject commit b5a8c68c4262e5d9d7f8280c1d07c252a5e8dbf8 +Subproject commit a38b42788883fa2094f67874f15594044be1ac4d