From 18d48869a075cc8316b7d45d9aa98d2221ea25dd Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Thu, 23 Apr 2020 14:11:48 +0100 Subject: [PATCH] the great airgapping of recipes and itemstacks --- src/crafting/CraftingManager.php | 54 ++++- src/entity/Human.php | 3 +- src/entity/object/ItemEntity.php | 3 +- src/network/mcpe/InventoryManager.php | 14 +- src/network/mcpe/NetworkSession.php | 12 +- src/network/mcpe/convert/TypeConverter.php | 124 ++++++++++++ .../mcpe/handler/InGamePacketHandler.php | 7 +- .../mcpe/handler/LoginPacketHandler.php | 1 + .../mcpe/protocol/AddItemActorPacket.php | 4 +- src/network/mcpe/protocol/AddPlayerPacket.php | 4 +- .../mcpe/protocol/CraftingDataPacket.php | 189 ++---------------- .../mcpe/protocol/CraftingEventPacket.php | 6 +- .../mcpe/protocol/InventoryContentPacket.php | 6 +- .../mcpe/protocol/InventorySlotPacket.php | 7 +- .../mcpe/protocol/MobArmorEquipmentPacket.php | 13 +- .../mcpe/protocol/MobEquipmentPacket.php | 6 +- .../protocol/types/inventory/ItemStack.php | 100 +++++++++ .../inventory/NetworkInventoryAction.php | 18 +- .../inventory/ReleaseItemTransactionData.php | 8 +- .../UseItemOnEntityTransactionData.php | 7 +- .../inventory/UseItemTransactionData.php | 7 +- .../protocol/types/recipe/FurnaceRecipe.php | 85 ++++++++ .../protocol/types/recipe/MultiRecipe.php | 50 +++++ .../types/recipe/RecipeIngredient.php | 52 +++++ .../types/recipe/RecipeWithTypeId.php | 41 ++++ .../protocol/types/recipe/ShapedRecipe.php | 151 ++++++++++++++ .../protocol/types/recipe/ShapelessRecipe.php | 123 ++++++++++++ .../mcpe/serializer/NetworkBinaryStream.php | 107 ++++------ src/world/particle/FloatingTextParticle.php | 4 +- 29 files changed, 907 insertions(+), 299 deletions(-) create mode 100644 src/network/mcpe/convert/TypeConverter.php create mode 100644 src/network/mcpe/protocol/types/inventory/ItemStack.php create mode 100644 src/network/mcpe/protocol/types/recipe/FurnaceRecipe.php create mode 100644 src/network/mcpe/protocol/types/recipe/MultiRecipe.php create mode 100644 src/network/mcpe/protocol/types/recipe/RecipeIngredient.php create mode 100644 src/network/mcpe/protocol/types/recipe/RecipeWithTypeId.php create mode 100644 src/network/mcpe/protocol/types/recipe/ShapedRecipe.php create mode 100644 src/network/mcpe/protocol/types/recipe/ShapelessRecipe.php diff --git a/src/crafting/CraftingManager.php b/src/crafting/CraftingManager.php index 4733f7df7..c3243176d 100644 --- a/src/crafting/CraftingManager.php +++ b/src/crafting/CraftingManager.php @@ -26,13 +26,22 @@ namespace pocketmine\crafting; use pocketmine\item\Item; use pocketmine\network\mcpe\compression\CompressBatchPromise; use pocketmine\network\mcpe\compression\Zlib; +use pocketmine\network\mcpe\convert\TypeConverter; use pocketmine\network\mcpe\PacketBatch; use pocketmine\network\mcpe\protocol\CraftingDataPacket; +use pocketmine\network\mcpe\protocol\types\inventory\ItemStack; +use pocketmine\network\mcpe\protocol\types\recipe\FurnaceRecipe as ProtocolFurnaceRecipe; +use pocketmine\network\mcpe\protocol\types\recipe\RecipeIngredient; +use pocketmine\network\mcpe\protocol\types\recipe\ShapedRecipe as ProtocolShapedRecipe; +use pocketmine\network\mcpe\protocol\types\recipe\ShapelessRecipe as ProtocolShapelessRecipe; use pocketmine\timings\Timings; +use pocketmine\utils\Binary; +use pocketmine\utils\UUID; use function array_map; use function file_get_contents; use function json_decode; use function json_encode; +use function str_repeat; use function usort; use const DIRECTORY_SEPARATOR; @@ -101,19 +110,58 @@ class CraftingManager{ $pk = new CraftingDataPacket(); $pk->cleanRecipes = true; + $counter = 0; + $nullUUID = UUID::fromData(str_repeat("\x00", 16)); + $converter = TypeConverter::getInstance(); foreach($this->shapelessRecipes as $list){ foreach($list as $recipe){ - $pk->addShapelessRecipe($recipe); + $pk->entries[] = new ProtocolShapelessRecipe( + CraftingDataPacket::ENTRY_SHAPELESS, + Binary::writeInt($counter++), + array_map(function(Item $item) use ($converter) : RecipeIngredient{ + return $converter->coreItemStackToRecipeIngredient($item); + }, $recipe->getIngredientList()), + array_map(function(Item $item) use ($converter) : ItemStack{ + return $converter->coreItemStackToNet($item); + }, $recipe->getResults()), + $nullUUID, + "crafting_table", + 50 + ); } } foreach($this->shapedRecipes as $list){ foreach($list as $recipe){ - $pk->addShapedRecipe($recipe); + $inputs = []; + + for($row = 0, $height = $recipe->getHeight(); $row < $height; ++$row){ + for($column = 0, $width = $recipe->getWidth(); $column < $width; ++$column){ + $inputs[$row][$column] = $converter->coreItemStackToRecipeIngredient($recipe->getIngredient($column, $row)); + } + } + $pk->entries[] = $r = new ProtocolShapedRecipe( + CraftingDataPacket::ENTRY_SHAPED, + Binary::writeInt($counter++), + $inputs, + array_map(function(Item $item) use ($converter) : ItemStack{ + return $converter->coreItemStackToNet($item); + }, $recipe->getResults()), + $nullUUID, + "crafting_table", + 50 + ); } } foreach($this->furnaceRecipes as $recipe){ - $pk->addFurnaceRecipe($recipe); + $input = $converter->coreItemStackToNet($recipe->getInput()); + $pk->entries[] = new ProtocolFurnaceRecipe( + CraftingDataPacket::ENTRY_FURNACE_DATA, + $input->getId(), + $input->getMeta(), + $converter->coreItemStackToNet($recipe->getResult()), + "furnace" + ); } $this->craftingDataCache = new CompressBatchPromise(); diff --git a/src/entity/Human.php b/src/entity/Human.php index a3e1db0da..c85e9ee57 100644 --- a/src/entity/Human.php +++ b/src/entity/Human.php @@ -41,6 +41,7 @@ use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\IntTag; use pocketmine\nbt\tag\ListTag; use pocketmine\nbt\tag\StringTag; +use pocketmine\network\mcpe\convert\TypeConverter; use pocketmine\network\mcpe\protocol\ActorEventPacket; use pocketmine\network\mcpe\protocol\AddPlayerPacket; use pocketmine\network\mcpe\protocol\MovePlayerPacket; @@ -403,7 +404,7 @@ class Human extends Living implements ProjectileSource, InventoryHolder{ $pk->motion = $this->getMotion(); $pk->yaw = $this->location->yaw; $pk->pitch = $this->location->pitch; - $pk->item = $this->getInventory()->getItemInHand(); + $pk->item = TypeConverter::getInstance()->coreItemStackToNet($this->getInventory()->getItemInHand()); $pk->metadata = $this->getSyncedNetworkData(false); $player->getNetworkSession()->sendDataPacket($pk); diff --git a/src/entity/object/ItemEntity.php b/src/entity/object/ItemEntity.php index 32b2ba805..6ddf055a3 100644 --- a/src/entity/object/ItemEntity.php +++ b/src/entity/object/ItemEntity.php @@ -29,6 +29,7 @@ use pocketmine\event\entity\ItemSpawnEvent; use pocketmine\event\inventory\InventoryPickupItemEvent; use pocketmine\item\Item; use pocketmine\nbt\tag\CompoundTag; +use pocketmine\network\mcpe\convert\TypeConverter; use pocketmine\network\mcpe\protocol\AddItemActorPacket; use pocketmine\network\mcpe\protocol\TakeItemActorPacket; use pocketmine\network\mcpe\protocol\types\entity\EntityLegacyIds; @@ -210,7 +211,7 @@ class ItemEntity extends Entity{ $pk->entityRuntimeId = $this->getId(); $pk->position = $this->location->asVector3(); $pk->motion = $this->getMotion(); - $pk->item = $this->getItem(); + $pk->item = TypeConverter::getInstance()->coreItemStackToNet($this->getItem()); $pk->metadata = $this->getSyncedNetworkData(false); $player->getNetworkSession()->sendDataPacket($pk); diff --git a/src/network/mcpe/InventoryManager.php b/src/network/mcpe/InventoryManager.php index 61f68142e..b589e5740 100644 --- a/src/network/mcpe/InventoryManager.php +++ b/src/network/mcpe/InventoryManager.php @@ -34,6 +34,7 @@ use pocketmine\inventory\Inventory; use pocketmine\inventory\transaction\action\SlotChangeAction; use pocketmine\inventory\transaction\InventoryTransaction; use pocketmine\item\Item; +use pocketmine\network\mcpe\convert\TypeConverter; use pocketmine\network\mcpe\protocol\ContainerClosePacket; use pocketmine\network\mcpe\protocol\ContainerOpenPacket; use pocketmine\network\mcpe\protocol\ContainerSetDataPacket; @@ -41,6 +42,7 @@ use pocketmine\network\mcpe\protocol\InventoryContentPacket; use pocketmine\network\mcpe\protocol\InventorySlotPacket; use pocketmine\network\mcpe\protocol\MobEquipmentPacket; use pocketmine\network\mcpe\protocol\types\inventory\ContainerIds; +use pocketmine\network\mcpe\protocol\types\inventory\ItemStack; use pocketmine\network\mcpe\protocol\types\inventory\WindowTypes; use pocketmine\player\Player; use function array_search; @@ -159,7 +161,7 @@ class InventoryManager{ $currentItem = $inventory->getItem($slot); $clientSideItem = $this->initiatedSlotChanges[$windowId][$slot] ?? null; if($clientSideItem === null or !$clientSideItem->equalsExact($currentItem)){ - $this->session->sendDataPacket(InventorySlotPacket::create($windowId, $slot, $currentItem)); + $this->session->sendDataPacket(InventorySlotPacket::create($windowId, $slot, TypeConverter::getInstance()->coreItemStackToNet($currentItem))); } unset($this->initiatedSlotChanges[$windowId][$slot]); } @@ -169,7 +171,10 @@ class InventoryManager{ $windowId = $this->getWindowId($inventory); if($windowId !== null){ unset($this->initiatedSlotChanges[$windowId]); - $this->session->sendDataPacket(InventoryContentPacket::create($windowId, $inventory->getContents(true))); + $typeConverter = TypeConverter::getInstance(); + $this->session->sendDataPacket(InventoryContentPacket::create($windowId, array_map(function(Item $itemStack) use ($typeConverter) : ItemStack{ + return $typeConverter->coreItemStackToNet($itemStack); + }, $inventory->getContents(true)))); } } @@ -189,7 +194,7 @@ class InventoryManager{ public function syncSelectedHotbarSlot() : void{ $this->session->sendDataPacket(MobEquipmentPacket::create( $this->player->getId(), - $this->player->getInventory()->getItemInHand(), + TypeConverter::getInstance()->coreItemStackToNet($this->player->getInventory()->getItemInHand()), $this->player->getInventory()->getHeldItemIndex(), ContainerIds::INVENTORY )); @@ -197,9 +202,10 @@ class InventoryManager{ public function syncCreative() : void{ $items = []; + $typeConverter = TypeConverter::getInstance(); if(!$this->player->isSpectator()){ //fill it for all gamemodes except spectator foreach(CreativeInventory::getAll() as $i => $item){ - $items[$i] = clone $item; + $items[$i] = $typeConverter->coreItemStackToNet($item); } } diff --git a/src/network/mcpe/NetworkSession.php b/src/network/mcpe/NetworkSession.php index 5da1cf2cf..4b81e1c3a 100644 --- a/src/network/mcpe/NetworkSession.php +++ b/src/network/mcpe/NetworkSession.php @@ -37,6 +37,7 @@ use pocketmine\network\BadPacketException; use pocketmine\network\mcpe\compression\CompressBatchPromise; use pocketmine\network\mcpe\compression\DecompressionException; use pocketmine\network\mcpe\compression\Zlib; +use pocketmine\network\mcpe\convert\TypeConverter; use pocketmine\network\mcpe\encryption\DecryptionException; use pocketmine\network\mcpe\encryption\NetworkCipher; use pocketmine\network\mcpe\encryption\PrepareEncryptionTask; @@ -790,12 +791,19 @@ class NetworkSession{ public function onMobEquipmentChange(Human $mob) : void{ //TODO: we could send zero for slot here because remote players don't need to know which slot was selected $inv = $mob->getInventory(); - $this->sendDataPacket(MobEquipmentPacket::create($mob->getId(), $inv->getItemInHand(), $inv->getHeldItemIndex(), ContainerIds::INVENTORY)); + $this->sendDataPacket(MobEquipmentPacket::create($mob->getId(), TypeConverter::getInstance()->coreItemStackToNet($inv->getItemInHand()), $inv->getHeldItemIndex(), ContainerIds::INVENTORY)); } public function onMobArmorChange(Living $mob) : void{ $inv = $mob->getArmorInventory(); - $this->sendDataPacket(MobArmorEquipmentPacket::create($mob->getId(), $inv->getHelmet(), $inv->getChestplate(), $inv->getLeggings(), $inv->getBoots())); + $converter = TypeConverter::getInstance(); + $this->sendDataPacket(MobArmorEquipmentPacket::create( + $mob->getId(), + $converter->coreItemStackToNet($inv->getHelmet()), + $converter->coreItemStackToNet($inv->getChestplate()), + $converter->coreItemStackToNet($inv->getLeggings()), + $converter->coreItemStackToNet($inv->getBoots()) + )); } public function syncPlayerList() : void{ diff --git a/src/network/mcpe/convert/TypeConverter.php b/src/network/mcpe/convert/TypeConverter.php new file mode 100644 index 000000000..775095a56 --- /dev/null +++ b/src/network/mcpe/convert/TypeConverter.php @@ -0,0 +1,124 @@ +getMeta(); + return new RecipeIngredient($itemStack->getId(), $meta === -1 ? 0x7fff : $meta, $itemStack->getCount()); + } + + public function recipeIngredientToCoreItemStack(RecipeIngredient $ingredient) : Item{ + $meta = $ingredient->getMeta(); + return ItemFactory::get($ingredient->getId(), $meta === 0x7fff ? -1 : $meta, $ingredient->getCount()); + } + + public function coreItemStackToNet(Item $itemStack) : ItemStack{ + $nbt = null; + if($itemStack->hasNamedTag()){ + $nbt = clone $itemStack->getNamedTag(); + } + if($itemStack instanceof Durable and $itemStack->getDamage() > 0){ + if($nbt !== null){ + if(($existing = $nbt->getTag(self::DAMAGE_TAG)) !== null){ + $nbt->removeTag(self::DAMAGE_TAG); + $nbt->setTag(self::DAMAGE_TAG_CONFLICT_RESOLUTION, $existing); + } + }else{ + $nbt = new CompoundTag(); + } + $nbt->setInt(self::DAMAGE_TAG, $itemStack->getDamage()); + } + $id = $itemStack->getId(); + $meta = $itemStack->getMeta(); + + return new ItemStack( + $id, + $meta === -1 ? 0x7fff : $meta, + $itemStack->getCount(), + $nbt, + [], + [], + $id === ItemIds::SHIELD ? 0 : null + ); + } + + public function netItemStackToCore(ItemStack $itemStack) : Item{ + $compound = $itemStack->getNbt(); + $meta = $itemStack->getMeta(); + + if($compound !== null){ + $compound = clone $compound; + if($compound->hasTag(self::DAMAGE_TAG, IntTag::class)){ + $meta = $compound->getInt(self::DAMAGE_TAG); + $compound->removeTag(self::DAMAGE_TAG); + if($compound->count() === 0){ + $compound = null; + goto end; + } + } + if(($conflicted = $compound->getTag(self::DAMAGE_TAG_CONFLICT_RESOLUTION)) !== null){ + $compound->removeTag(self::DAMAGE_TAG_CONFLICT_RESOLUTION); + $compound->setTag(self::DAMAGE_TAG, $conflicted); + } + } + + end: + return ItemFactory::get( + $itemStack->getId(), + $meta !== 0x7fff ? $meta : -1, + $itemStack->getCount(), + $compound + ); + } +} \ No newline at end of file diff --git a/src/network/mcpe/handler/InGamePacketHandler.php b/src/network/mcpe/handler/InGamePacketHandler.php index 75b124f85..70c03c1c5 100644 --- a/src/network/mcpe/handler/InGamePacketHandler.php +++ b/src/network/mcpe/handler/InGamePacketHandler.php @@ -38,6 +38,7 @@ use pocketmine\math\Vector3; use pocketmine\nbt\NbtDataException; use pocketmine\nbt\tag\StringTag; use pocketmine\network\BadPacketException; +use pocketmine\network\mcpe\convert\TypeConverter; use pocketmine\network\mcpe\NetworkSession; use pocketmine\network\mcpe\protocol\ActorEventPacket; use pocketmine\network\mcpe\protocol\ActorFallPacket; @@ -205,14 +206,16 @@ class InGamePacketHandler extends PacketHandler{ $isCrafting = false; $isFinalCraftingPart = false; foreach($data->getActions() as $networkInventoryAction){ + $old = TypeConverter::getInstance()->netItemStackToCore($networkInventoryAction->oldItem); + $new = TypeConverter::getInstance()->netItemStackToCore($networkInventoryAction->newItem); if( $networkInventoryAction->sourceType === NetworkInventoryAction::SOURCE_CONTAINER and $networkInventoryAction->windowId === ContainerIds::UI and $networkInventoryAction->inventorySlot === 50 and - !$networkInventoryAction->oldItem->equalsExact($networkInventoryAction->newItem) + !$old->equalsExact($new) ){ $isCrafting = true; - if(!$networkInventoryAction->oldItem->isNull() and $networkInventoryAction->newItem->isNull()){ + if(!$old->isNull() and $new->isNull()){ $isFinalCraftingPart = true; } }elseif( diff --git a/src/network/mcpe/handler/LoginPacketHandler.php b/src/network/mcpe/handler/LoginPacketHandler.php index 7f18fbb68..87d720df1 100644 --- a/src/network/mcpe/handler/LoginPacketHandler.php +++ b/src/network/mcpe/handler/LoginPacketHandler.php @@ -43,6 +43,7 @@ use pocketmine\player\Player; use pocketmine\player\PlayerInfo; use pocketmine\Server; use pocketmine\utils\UUID; +use function array_map; use function base64_decode; /** diff --git a/src/network/mcpe/protocol/AddItemActorPacket.php b/src/network/mcpe/protocol/AddItemActorPacket.php index 71ceab391..e174bad9f 100644 --- a/src/network/mcpe/protocol/AddItemActorPacket.php +++ b/src/network/mcpe/protocol/AddItemActorPacket.php @@ -25,10 +25,10 @@ namespace pocketmine\network\mcpe\protocol; #include -use pocketmine\item\Item; use pocketmine\math\Vector3; use pocketmine\network\mcpe\handler\PacketHandler; use pocketmine\network\mcpe\protocol\types\entity\MetadataProperty; +use pocketmine\network\mcpe\protocol\types\inventory\ItemStack; use pocketmine\network\mcpe\serializer\NetworkBinaryStream; class AddItemActorPacket extends DataPacket implements ClientboundPacket{ @@ -38,7 +38,7 @@ class AddItemActorPacket extends DataPacket implements ClientboundPacket{ public $entityUniqueId = null; //TODO /** @var int */ public $entityRuntimeId; - /** @var Item */ + /** @var ItemStack */ public $item; /** @var Vector3 */ public $position; diff --git a/src/network/mcpe/protocol/AddPlayerPacket.php b/src/network/mcpe/protocol/AddPlayerPacket.php index 453b10dbe..6e1475fc7 100644 --- a/src/network/mcpe/protocol/AddPlayerPacket.php +++ b/src/network/mcpe/protocol/AddPlayerPacket.php @@ -25,11 +25,11 @@ namespace pocketmine\network\mcpe\protocol; #include -use pocketmine\item\Item; use pocketmine\math\Vector3; use pocketmine\network\mcpe\handler\PacketHandler; use pocketmine\network\mcpe\protocol\types\entity\EntityLink; use pocketmine\network\mcpe\protocol\types\entity\MetadataProperty; +use pocketmine\network\mcpe\protocol\types\inventory\ItemStack; use pocketmine\network\mcpe\serializer\NetworkBinaryStream; use pocketmine\utils\UUID; use function count; @@ -57,7 +57,7 @@ class AddPlayerPacket extends DataPacket implements ClientboundPacket{ public $yaw = 0.0; /** @var float|null */ public $headYaw = null; //TODO - /** @var Item */ + /** @var ItemStack */ public $item; /** * @var MetadataProperty[] diff --git a/src/network/mcpe/protocol/CraftingDataPacket.php b/src/network/mcpe/protocol/CraftingDataPacket.php index af504e8ed..f94cb40ad 100644 --- a/src/network/mcpe/protocol/CraftingDataPacket.php +++ b/src/network/mcpe/protocol/CraftingDataPacket.php @@ -25,21 +25,17 @@ namespace pocketmine\network\mcpe\protocol; #include -use pocketmine\crafting\FurnaceRecipe; -use pocketmine\crafting\ShapedRecipe; -use pocketmine\crafting\ShapelessRecipe; -use pocketmine\item\Item; -use pocketmine\item\ItemFactory; use pocketmine\network\BadPacketException; use pocketmine\network\mcpe\handler\PacketHandler; use pocketmine\network\mcpe\protocol\types\PotionContainerChangeRecipe; use pocketmine\network\mcpe\protocol\types\PotionTypeRecipe; +use pocketmine\network\mcpe\protocol\types\recipe\FurnaceRecipe; +use pocketmine\network\mcpe\protocol\types\recipe\MultiRecipe; +use pocketmine\network\mcpe\protocol\types\recipe\RecipeWithTypeId; +use pocketmine\network\mcpe\protocol\types\recipe\ShapedRecipe; +use pocketmine\network\mcpe\protocol\types\recipe\ShapelessRecipe; use pocketmine\network\mcpe\serializer\NetworkBinaryStream; -#ifndef COMPILE -use pocketmine\utils\Binary; -#endif use function count; -use function str_repeat; class CraftingDataPacket extends DataPacket implements ClientboundPacket{ public const NETWORK_ID = ProtocolInfo::CRAFTING_DATA_PACKET; @@ -48,12 +44,12 @@ class CraftingDataPacket extends DataPacket implements ClientboundPacket{ public const ENTRY_SHAPED = 1; public const ENTRY_FURNACE = 2; public const ENTRY_FURNACE_DATA = 3; - public const ENTRY_MULTI = 4; //TODO - public const ENTRY_SHULKER_BOX = 5; //TODO - public const ENTRY_SHAPELESS_CHEMISTRY = 6; //TODO - public const ENTRY_SHAPED_CHEMISTRY = 7; //TODO + public const ENTRY_MULTI = 4; + public const ENTRY_SHULKER_BOX = 5; + public const ENTRY_SHAPELESS_CHEMISTRY = 6; + public const ENTRY_SHAPED_CHEMISTRY = 7; - /** @var object[] */ + /** @var RecipeWithTypeId[] */ public $entries = []; /** @var PotionTypeRecipe[] */ public $potionTypeRecipes = []; @@ -62,88 +58,31 @@ class CraftingDataPacket extends DataPacket implements ClientboundPacket{ /** @var bool */ public $cleanRecipes = false; - /** @var mixed[][] */ - public $decodedEntries = []; - protected function decodePayload(NetworkBinaryStream $in) : void{ - $this->decodedEntries = []; $recipeCount = $in->getUnsignedVarInt(); for($i = 0; $i < $recipeCount; ++$i){ - $entry = []; - $entry["type"] = $recipeType = $in->getVarInt(); + $recipeType = $in->getVarInt(); switch($recipeType){ case self::ENTRY_SHAPELESS: case self::ENTRY_SHULKER_BOX: case self::ENTRY_SHAPELESS_CHEMISTRY: - $entry["recipe_id"] = $in->getString(); - $ingredientCount = $in->getUnsignedVarInt(); - /** @var Item */ - $entry["input"] = []; - for($j = 0; $j < $ingredientCount; ++$j){ - $entry["input"][] = $input = $in->getRecipeIngredient(); - $input->setCount(1); //TODO HACK: they send a useless count field which breaks the PM crafting system because it isn't always 1 - } - $resultCount = $in->getUnsignedVarInt(); - $entry["output"] = []; - for($k = 0; $k < $resultCount; ++$k){ - $entry["output"][] = $in->getSlot(); - } - $entry["uuid"] = $in->getUUID()->toString(); - $entry["block"] = $in->getString(); - $entry["priority"] = $in->getVarInt(); - + $this->entries[] = ShapelessRecipe::decode($recipeType, $in); break; case self::ENTRY_SHAPED: case self::ENTRY_SHAPED_CHEMISTRY: - $entry["recipe_id"] = $in->getString(); - $entry["width"] = $in->getVarInt(); - $entry["height"] = $in->getVarInt(); - $count = $entry["width"] * $entry["height"]; - $entry["input"] = []; - for($j = 0; $j < $count; ++$j){ - $entry["input"][] = $input = $in->getRecipeIngredient(); - $input->setCount(1); //TODO HACK: they send a useless count field which breaks the PM crafting system - } - $resultCount = $in->getUnsignedVarInt(); - $entry["output"] = []; - for($k = 0; $k < $resultCount; ++$k){ - $entry["output"][] = $in->getSlot(); - } - $entry["uuid"] = $in->getUUID()->toString(); - $entry["block"] = $in->getString(); - $entry["priority"] = $in->getVarInt(); - + $this->entries[] = ShapedRecipe::decode($recipeType, $in); break; case self::ENTRY_FURNACE: case self::ENTRY_FURNACE_DATA: - $inputId = $in->getVarInt(); - $inputData = -1; - if($recipeType === self::ENTRY_FURNACE_DATA){ - $inputData = $in->getVarInt(); - if($inputData === 0x7fff){ - $inputData = -1; - } - } - try{ - $entry["input"] = ItemFactory::get($inputId, $inputData); - }catch(\InvalidArgumentException $e){ - throw BadPacketException::wrap($e); - } - $entry["output"] = $out = $in->getSlot(); - if($out->getMeta() === 0x7fff){ - $entry["output"] = ItemFactory::get($out->getId(), 0); //TODO HACK: some 1.12 furnace recipe outputs have wildcard damage values - } - $entry["block"] = $in->getString(); - + $this->entries[] = FurnaceRecipe::decode($recipeType, $in); break; case self::ENTRY_MULTI: - $entry["uuid"] = $in->getUUID()->toString(); + $this->entries[] = MultiRecipe::decode($recipeType, $in); break; default: throw new BadPacketException("Unhandled recipe type $recipeType!"); //do not continue attempting to decode } - $this->decodedEntries[] = $entry; } for($i = 0, $count = $in->getUnsignedVarInt(); $i < $count; ++$i){ $input = $in->getVarInt(); @@ -160,105 +99,11 @@ class CraftingDataPacket extends DataPacket implements ClientboundPacket{ $this->cleanRecipes = $in->getBool(); } - /** - * @param object $entry - */ - private static function writeEntry($entry, NetworkBinaryStream $stream, int $pos) : int{ - if($entry instanceof ShapelessRecipe){ - return self::writeShapelessRecipe($entry, $stream, $pos); - }elseif($entry instanceof ShapedRecipe){ - return self::writeShapedRecipe($entry, $stream, $pos); - }elseif($entry instanceof FurnaceRecipe){ - return self::writeFurnaceRecipe($entry, $stream); - } - //TODO: add MultiRecipe - - return -1; - } - - private static function writeShapelessRecipe(ShapelessRecipe $recipe, NetworkBinaryStream $stream, int $pos) : int{ - $stream->putString(Binary::writeInt($pos)); //some kind of recipe ID, doesn't matter what it is as long as it's unique - $stream->putUnsignedVarInt($recipe->getIngredientCount()); - foreach($recipe->getIngredientList() as $item){ - $stream->putRecipeIngredient($item); - } - - $results = $recipe->getResults(); - $stream->putUnsignedVarInt(count($results)); - foreach($results as $item){ - $stream->putSlot($item); - } - - $stream->put(str_repeat("\x00", 16)); //Null UUID - $stream->putString("crafting_table"); //TODO: blocktype (no prefix) (this might require internal API breaks) - $stream->putVarInt(50); //TODO: priority - - return CraftingDataPacket::ENTRY_SHAPELESS; - } - - private static function writeShapedRecipe(ShapedRecipe $recipe, NetworkBinaryStream $stream, int $pos) : int{ - $stream->putString(Binary::writeInt($pos)); //some kind of recipe ID, doesn't matter what it is as long as it's unique - $stream->putVarInt($recipe->getWidth()); - $stream->putVarInt($recipe->getHeight()); - - for($z = 0; $z < $recipe->getHeight(); ++$z){ - for($x = 0; $x < $recipe->getWidth(); ++$x){ - $stream->putRecipeIngredient($recipe->getIngredient($x, $z)); - } - } - - $results = $recipe->getResults(); - $stream->putUnsignedVarInt(count($results)); - foreach($results as $item){ - $stream->putSlot($item); - } - - $stream->put(str_repeat("\x00", 16)); //Null UUID - $stream->putString("crafting_table"); //TODO: blocktype (no prefix) (this might require internal API breaks) - $stream->putVarInt(50); //TODO: priority - - return CraftingDataPacket::ENTRY_SHAPED; - } - - private static function writeFurnaceRecipe(FurnaceRecipe $recipe, NetworkBinaryStream $stream) : int{ - $stream->putVarInt($recipe->getInput()->getId()); - $result = CraftingDataPacket::ENTRY_FURNACE; - if(!$recipe->getInput()->hasAnyDamageValue()){ //Data recipe - $stream->putVarInt($recipe->getInput()->getMeta()); - $result = CraftingDataPacket::ENTRY_FURNACE_DATA; - } - $stream->putSlot($recipe->getResult()); - $stream->putString("furnace"); //TODO: blocktype (no prefix) (this might require internal API breaks) - return $result; - } - - public function addShapelessRecipe(ShapelessRecipe $recipe) : void{ - $this->entries[] = $recipe; - } - - public function addShapedRecipe(ShapedRecipe $recipe) : void{ - $this->entries[] = $recipe; - } - - public function addFurnaceRecipe(FurnaceRecipe $recipe) : void{ - $this->entries[] = $recipe; - } - protected function encodePayload(NetworkBinaryStream $out) : void{ $out->putUnsignedVarInt(count($this->entries)); - - $writer = new NetworkBinaryStream(); - $counter = 0; foreach($this->entries as $d){ - $entryType = self::writeEntry($d, $writer, $counter++); - if($entryType >= 0){ - $out->putVarInt($entryType); - $out->put($writer->getBuffer()); - }else{ - $out->putVarInt(-1); - } - - $writer->reset(); + $out->putVarInt($d->getTypeId()); + $d->encode($out); } $out->putUnsignedVarInt(count($this->potionTypeRecipes)); foreach($this->potionTypeRecipes as $recipe){ diff --git a/src/network/mcpe/protocol/CraftingEventPacket.php b/src/network/mcpe/protocol/CraftingEventPacket.php index b932f839b..c9f357cf2 100644 --- a/src/network/mcpe/protocol/CraftingEventPacket.php +++ b/src/network/mcpe/protocol/CraftingEventPacket.php @@ -25,8 +25,8 @@ namespace pocketmine\network\mcpe\protocol; #include -use pocketmine\item\Item; use pocketmine\network\mcpe\handler\PacketHandler; +use pocketmine\network\mcpe\protocol\types\inventory\ItemStack; use pocketmine\network\mcpe\serializer\NetworkBinaryStream; use pocketmine\utils\UUID; use function count; @@ -40,9 +40,9 @@ class CraftingEventPacket extends DataPacket implements ServerboundPacket{ public $type; /** @var UUID */ public $id; - /** @var Item[] */ + /** @var ItemStack[] */ public $input = []; - /** @var Item[] */ + /** @var ItemStack[] */ public $output = []; protected function decodePayload(NetworkBinaryStream $in) : void{ diff --git a/src/network/mcpe/protocol/InventoryContentPacket.php b/src/network/mcpe/protocol/InventoryContentPacket.php index 2490b9b79..28b28d954 100644 --- a/src/network/mcpe/protocol/InventoryContentPacket.php +++ b/src/network/mcpe/protocol/InventoryContentPacket.php @@ -25,8 +25,8 @@ namespace pocketmine\network\mcpe\protocol; #include -use pocketmine\item\Item; use pocketmine\network\mcpe\handler\PacketHandler; +use pocketmine\network\mcpe\protocol\types\inventory\ItemStack; use pocketmine\network\mcpe\serializer\NetworkBinaryStream; use function count; @@ -35,11 +35,11 @@ class InventoryContentPacket extends DataPacket implements ClientboundPacket{ /** @var int */ public $windowId; - /** @var Item[] */ + /** @var ItemStack[] */ public $items = []; /** - * @param Item[] $items + * @param ItemStack[] $items * * @return InventoryContentPacket */ diff --git a/src/network/mcpe/protocol/InventorySlotPacket.php b/src/network/mcpe/protocol/InventorySlotPacket.php index 24632bc6e..485226bae 100644 --- a/src/network/mcpe/protocol/InventorySlotPacket.php +++ b/src/network/mcpe/protocol/InventorySlotPacket.php @@ -25,8 +25,8 @@ namespace pocketmine\network\mcpe\protocol; #include -use pocketmine\item\Item; use pocketmine\network\mcpe\handler\PacketHandler; +use pocketmine\network\mcpe\protocol\types\inventory\ItemStack; use pocketmine\network\mcpe\serializer\NetworkBinaryStream; class InventorySlotPacket extends DataPacket implements ClientboundPacket{ @@ -36,14 +36,15 @@ class InventorySlotPacket extends DataPacket implements ClientboundPacket{ public $windowId; /** @var int */ public $inventorySlot; - /** @var Item */ + /** @var ItemStack */ public $item; - public static function create(int $windowId, int $slot, Item $item) : self{ + public static function create(int $windowId, int $slot, ItemStack $item) : self{ $result = new self; $result->inventorySlot = $slot; $result->item = $item; $result->windowId = $windowId; + return $result; } diff --git a/src/network/mcpe/protocol/MobArmorEquipmentPacket.php b/src/network/mcpe/protocol/MobArmorEquipmentPacket.php index 1d3aba6af..255350bc2 100644 --- a/src/network/mcpe/protocol/MobArmorEquipmentPacket.php +++ b/src/network/mcpe/protocol/MobArmorEquipmentPacket.php @@ -25,8 +25,8 @@ namespace pocketmine\network\mcpe\protocol; #include -use pocketmine\item\Item; use pocketmine\network\mcpe\handler\PacketHandler; +use pocketmine\network\mcpe\protocol\types\inventory\ItemStack; use pocketmine\network\mcpe\serializer\NetworkBinaryStream; class MobArmorEquipmentPacket extends DataPacket implements ClientboundPacket, ServerboundPacket{ @@ -37,22 +37,23 @@ class MobArmorEquipmentPacket extends DataPacket implements ClientboundPacket, S //this intentionally doesn't use an array because we don't want any implicit dependencies on internal order - /** @var Item */ + /** @var ItemStack */ public $head; - /** @var Item */ + /** @var ItemStack */ public $chest; - /** @var Item */ + /** @var ItemStack */ public $legs; - /** @var Item */ + /** @var ItemStack */ public $feet; - public static function create(int $entityRuntimeId, Item $head, Item $chest, Item $legs, Item $feet) : self{ + public static function create(int $entityRuntimeId, ItemStack $head, ItemStack $chest, ItemStack $legs, ItemStack $feet) : self{ $result = new self; $result->entityRuntimeId = $entityRuntimeId; $result->head = $head; $result->chest = $chest; $result->legs = $legs; $result->feet = $feet; + return $result; } diff --git a/src/network/mcpe/protocol/MobEquipmentPacket.php b/src/network/mcpe/protocol/MobEquipmentPacket.php index 53e87632c..69d0f1ce4 100644 --- a/src/network/mcpe/protocol/MobEquipmentPacket.php +++ b/src/network/mcpe/protocol/MobEquipmentPacket.php @@ -25,8 +25,8 @@ namespace pocketmine\network\mcpe\protocol; #include -use pocketmine\item\Item; use pocketmine\network\mcpe\handler\PacketHandler; +use pocketmine\network\mcpe\protocol\types\inventory\ItemStack; use pocketmine\network\mcpe\serializer\NetworkBinaryStream; class MobEquipmentPacket extends DataPacket implements ClientboundPacket, ServerboundPacket{ @@ -34,7 +34,7 @@ class MobEquipmentPacket extends DataPacket implements ClientboundPacket, Server /** @var int */ public $entityRuntimeId; - /** @var Item */ + /** @var ItemStack */ public $item; /** @var int */ public $inventorySlot; @@ -43,7 +43,7 @@ class MobEquipmentPacket extends DataPacket implements ClientboundPacket, Server /** @var int */ public $windowId = 0; - public static function create(int $entityRuntimeId, Item $item, int $inventorySlot, int $windowId) : self{ + public static function create(int $entityRuntimeId, ItemStack $item, int $inventorySlot, int $windowId) : self{ $result = new self; $result->entityRuntimeId = $entityRuntimeId; $result->item = $item; diff --git a/src/network/mcpe/protocol/types/inventory/ItemStack.php b/src/network/mcpe/protocol/types/inventory/ItemStack.php new file mode 100644 index 000000000..1a745a0cb --- /dev/null +++ b/src/network/mcpe/protocol/types/inventory/ItemStack.php @@ -0,0 +1,100 @@ +id = $id; + $this->meta = $meta; + $this->count = $count; + $this->canPlaceOn = $canPlaceOn; + $this->canDestroy = $canDestroy; + $this->nbt = $nbt; + $this->shieldBlockingTick = $shieldBlockingTick; + } + + public static function null() : self{ + return new self(0, 0, 0, null, [], [], null); + } + + public function getId() : int{ + return $this->id; + } + + public function getMeta() : int{ + return $this->meta; + } + + public function getCount() : int{ + return $this->count; + } + + /** + * @return string[] + */ + public function getCanPlaceOn() : array{ + return $this->canPlaceOn; + } + + /** + * @return string[] + */ + public function getCanDestroy() : array{ + return $this->canDestroy; + } + + public function getNbt() : ?CompoundTag{ + return $this->nbt; + } + + public function getShieldBlockingTick() : ?int{ + return $this->shieldBlockingTick; + } +} diff --git a/src/network/mcpe/protocol/types/inventory/NetworkInventoryAction.php b/src/network/mcpe/protocol/types/inventory/NetworkInventoryAction.php index a4a2f46b8..db8b34db9 100644 --- a/src/network/mcpe/protocol/types/inventory/NetworkInventoryAction.php +++ b/src/network/mcpe/protocol/types/inventory/NetworkInventoryAction.php @@ -31,8 +31,8 @@ use pocketmine\inventory\transaction\action\DestroyItemAction; use pocketmine\inventory\transaction\action\DropItemAction; use pocketmine\inventory\transaction\action\InventoryAction; use pocketmine\inventory\transaction\action\SlotChangeAction; -use pocketmine\item\Item; use pocketmine\network\BadPacketException; +use pocketmine\network\mcpe\convert\TypeConverter; use pocketmine\network\mcpe\serializer\NetworkBinaryStream; use pocketmine\player\Player; use pocketmine\utils\BinaryDataException; @@ -82,9 +82,9 @@ class NetworkInventoryAction{ public $sourceFlags = 0; /** @var int */ public $inventorySlot; - /** @var Item */ + /** @var ItemStack */ public $oldItem; - /** @var Item */ + /** @var ItemStack */ public $newItem; /** @@ -150,7 +150,9 @@ class NetworkInventoryAction{ * @throws \UnexpectedValueException */ public function createInventoryAction(Player $player) : ?InventoryAction{ - if($this->oldItem->equalsExact($this->newItem)){ + $old = TypeConverter::getInstance()->netItemStackToCore($this->oldItem); + $new = TypeConverter::getInstance()->netItemStackToCore($this->newItem); + if($old->equalsExact($new)){ //filter out useless noise in 1.13 return null; } @@ -180,7 +182,7 @@ class NetworkInventoryAction{ $slot = $this->inventorySlot; } if($window !== null){ - return new SlotChangeAction($window, $slot, $this->oldItem, $this->newItem); + return new SlotChangeAction($window, $slot, $old, $new); } throw new \UnexpectedValueException("No open container with window ID $this->windowId"); @@ -189,13 +191,13 @@ class NetworkInventoryAction{ throw new \UnexpectedValueException("Only expecting drop-item world actions from the client!"); } - return new DropItemAction($this->newItem); + return new DropItemAction($new); case self::SOURCE_CREATIVE: switch($this->inventorySlot){ case self::ACTION_MAGIC_SLOT_CREATIVE_DELETE_ITEM: - return new DestroyItemAction($this->newItem); + return new DestroyItemAction($new); case self::ACTION_MAGIC_SLOT_CREATIVE_CREATE_ITEM: - return new CreateItemAction($this->oldItem); + return new CreateItemAction($new); default: throw new \UnexpectedValueException("Unexpected creative action type $this->inventorySlot"); diff --git a/src/network/mcpe/protocol/types/inventory/ReleaseItemTransactionData.php b/src/network/mcpe/protocol/types/inventory/ReleaseItemTransactionData.php index b0d8a4f6d..5b399b054 100644 --- a/src/network/mcpe/protocol/types/inventory/ReleaseItemTransactionData.php +++ b/src/network/mcpe/protocol/types/inventory/ReleaseItemTransactionData.php @@ -23,7 +23,6 @@ declare(strict_types=1); namespace pocketmine\network\mcpe\protocol\types\inventory; -use pocketmine\item\Item; use pocketmine\math\Vector3; use pocketmine\network\mcpe\protocol\InventoryTransactionPacket; use pocketmine\network\mcpe\serializer\NetworkBinaryStream; @@ -36,7 +35,7 @@ class ReleaseItemTransactionData extends TransactionData{ private $actionType; /** @var int */ private $hotbarSlot; - /** @var Item */ + /** @var ItemStack */ private $itemInHand; /** @var Vector3 */ private $headPos; @@ -49,7 +48,7 @@ class ReleaseItemTransactionData extends TransactionData{ return $this->hotbarSlot; } - public function getItemInHand() : Item{ + public function getItemInHand() : ItemStack{ return $this->itemInHand; } @@ -78,13 +77,14 @@ class ReleaseItemTransactionData extends TransactionData{ /** * @param NetworkInventoryAction[] $actions */ - public static function new(array $actions, int $actionType, int $hotbarSlot, Item $itemInHand, Vector3 $headPos) : self{ + public static function new(array $actions, int $actionType, int $hotbarSlot, ItemStack $itemInHand, Vector3 $headPos) : self{ $result = new self; $result->actions = $actions; $result->actionType = $actionType; $result->hotbarSlot = $hotbarSlot; $result->itemInHand = $itemInHand; $result->headPos = $headPos; + return $result; } } diff --git a/src/network/mcpe/protocol/types/inventory/UseItemOnEntityTransactionData.php b/src/network/mcpe/protocol/types/inventory/UseItemOnEntityTransactionData.php index c53408cd0..005fc1409 100644 --- a/src/network/mcpe/protocol/types/inventory/UseItemOnEntityTransactionData.php +++ b/src/network/mcpe/protocol/types/inventory/UseItemOnEntityTransactionData.php @@ -23,7 +23,6 @@ declare(strict_types=1); namespace pocketmine\network\mcpe\protocol\types\inventory; -use pocketmine\item\Item; use pocketmine\math\Vector3; use pocketmine\network\mcpe\protocol\InventoryTransactionPacket; use pocketmine\network\mcpe\serializer\NetworkBinaryStream; @@ -38,7 +37,7 @@ class UseItemOnEntityTransactionData extends TransactionData{ private $actionType; /** @var int */ private $hotbarSlot; - /** @var Item */ + /** @var ItemStack */ private $itemInHand; /** @var Vector3 */ private $playerPos; @@ -57,7 +56,7 @@ class UseItemOnEntityTransactionData extends TransactionData{ return $this->hotbarSlot; } - public function getItemInHand() : Item{ + public function getItemInHand() : ItemStack{ return $this->itemInHand; } @@ -94,7 +93,7 @@ class UseItemOnEntityTransactionData extends TransactionData{ /** * @param NetworkInventoryAction[] $actions */ - public static function new(array $actions, int $entityRuntimeId, int $actionType, int $hotbarSlot, Item $itemInHand, Vector3 $playerPos, Vector3 $clickPos) : self{ + public static function new(array $actions, int $entityRuntimeId, int $actionType, int $hotbarSlot, ItemStack $itemInHand, Vector3 $playerPos, Vector3 $clickPos) : self{ $result = new self; $result->actions = $actions; $result->entityRuntimeId = $entityRuntimeId; diff --git a/src/network/mcpe/protocol/types/inventory/UseItemTransactionData.php b/src/network/mcpe/protocol/types/inventory/UseItemTransactionData.php index 8131b6aa0..71d832b1e 100644 --- a/src/network/mcpe/protocol/types/inventory/UseItemTransactionData.php +++ b/src/network/mcpe/protocol/types/inventory/UseItemTransactionData.php @@ -23,7 +23,6 @@ declare(strict_types=1); namespace pocketmine\network\mcpe\protocol\types\inventory; -use pocketmine\item\Item; use pocketmine\math\Vector3; use pocketmine\network\mcpe\protocol\InventoryTransactionPacket; use pocketmine\network\mcpe\serializer\NetworkBinaryStream; @@ -41,7 +40,7 @@ class UseItemTransactionData extends TransactionData{ private $face; /** @var int */ private $hotbarSlot; - /** @var Item */ + /** @var ItemStack */ private $itemInHand; /** @var Vector3 */ private $playerPos; @@ -66,7 +65,7 @@ class UseItemTransactionData extends TransactionData{ return $this->hotbarSlot; } - public function getItemInHand() : Item{ + public function getItemInHand() : ItemStack{ return $this->itemInHand; } @@ -112,7 +111,7 @@ class UseItemTransactionData extends TransactionData{ /** * @param NetworkInventoryAction[] $actions */ - public static function new(array $actions, int $actionType, Vector3 $blockPos, int $face, int $hotbarSlot, Item $itemInHand, Vector3 $playerPos, Vector3 $clickPos, int $blockRuntimeId) : self{ + public static function new(array $actions, int $actionType, Vector3 $blockPos, int $face, int $hotbarSlot, ItemStack $itemInHand, Vector3 $playerPos, Vector3 $clickPos, int $blockRuntimeId) : self{ $result = new self; $result->actions = $actions; $result->actionType = $actionType; diff --git a/src/network/mcpe/protocol/types/recipe/FurnaceRecipe.php b/src/network/mcpe/protocol/types/recipe/FurnaceRecipe.php new file mode 100644 index 000000000..163e54668 --- /dev/null +++ b/src/network/mcpe/protocol/types/recipe/FurnaceRecipe.php @@ -0,0 +1,85 @@ +inputId = $inputId; + $this->inputMeta = $inputMeta; + $this->result = $result; + $this->blockName = $blockName; + } + + public function getInputId() : int{ + return $this->inputId; + } + + public function getInputMeta() : ?int{ + return $this->inputMeta; + } + + public function getResult() : ItemStack{ + return $this->result; + } + + public function getBlockName() : string{ + return $this->blockName; + } + + public static function decode(int $typeId, NetworkBinaryStream $in) : self{ + $inputId = $in->getVarInt(); + $inputData = null; + if($typeId === CraftingDataPacket::ENTRY_FURNACE_DATA){ + $inputData = $in->getVarInt(); + } + $output = $in->getSlot(); + $block = $in->getString(); + + return new self($typeId, $inputId, $inputData, $output, $block); + } + + public function encode(NetworkBinaryStream $out) : void{ + $out->putVarInt($this->inputId); + if($this->getTypeId() === CraftingDataPacket::ENTRY_FURNACE_DATA){ + $out->putVarInt($this->inputMeta); + } + $out->putSlot($this->result); + $out->putString($this->blockName); + } +} diff --git a/src/network/mcpe/protocol/types/recipe/MultiRecipe.php b/src/network/mcpe/protocol/types/recipe/MultiRecipe.php new file mode 100644 index 000000000..24bc370d7 --- /dev/null +++ b/src/network/mcpe/protocol/types/recipe/MultiRecipe.php @@ -0,0 +1,50 @@ +recipeId = $recipeId; + } + + public function getRecipeId() : UUID{ + return $this->recipeId; + } + + public static function decode(int $typeId, NetworkBinaryStream $in) : self{ + return new self($typeId, $in->getUUID()); + } + + public function encode(NetworkBinaryStream $out) : void{ + $out->putUUID($this->recipeId); + } +} diff --git a/src/network/mcpe/protocol/types/recipe/RecipeIngredient.php b/src/network/mcpe/protocol/types/recipe/RecipeIngredient.php new file mode 100644 index 000000000..1349e2634 --- /dev/null +++ b/src/network/mcpe/protocol/types/recipe/RecipeIngredient.php @@ -0,0 +1,52 @@ +id = $id; + $this->meta = $meta; + $this->count = $count; + } + + public function getId() : int{ + return $this->id; + } + + public function getMeta() : int{ + return $this->meta; + } + + public function getCount() : int{ + return $this->count; + } +} diff --git a/src/network/mcpe/protocol/types/recipe/RecipeWithTypeId.php b/src/network/mcpe/protocol/types/recipe/RecipeWithTypeId.php new file mode 100644 index 000000000..9b61c0c14 --- /dev/null +++ b/src/network/mcpe/protocol/types/recipe/RecipeWithTypeId.php @@ -0,0 +1,41 @@ +typeId = $typeId; + } + + final public function getTypeId() : int{ + return $this->typeId; + } + + abstract public function encode(NetworkBinaryStream $out) : void; +} diff --git a/src/network/mcpe/protocol/types/recipe/ShapedRecipe.php b/src/network/mcpe/protocol/types/recipe/ShapedRecipe.php new file mode 100644 index 000000000..b18e82246 --- /dev/null +++ b/src/network/mcpe/protocol/types/recipe/ShapedRecipe.php @@ -0,0 +1,151 @@ + 3){ + throw new \InvalidArgumentException("Expected 1, 2 or 3 input rows"); + } + $columns = null; + foreach($input as $rowNumber => $row){ + if($columns === null){ + $columns = count($row); + }elseif(count($row) !== $columns){ + throw new \InvalidArgumentException("Expected each row to be $columns columns, but have " . count($row) . " in row $rowNumber"); + } + } + $this->recipeId = $recipeId; + $this->input = $input; + $this->output = $output; + $this->blockName = $blockType; + $this->priority = $priority; + $this->uuid = $uuid; + } + + public function getRecipeId() : string{ + return $this->recipeId; + } + + public function getWidth() : int{ + return count($this->input[0]); + } + + public function getHeight() : int{ + return count($this->input); + } + + /** + * @return RecipeIngredient[][] + */ + public function getInput() : array{ + return $this->input; + } + + /** + * @return ItemStack[] + */ + public function getOutput() : array{ + return $this->output; + } + + public function getUuid() : UUID{ + return $this->uuid; + } + + public function getBlockName() : string{ + return $this->blockName; + } + + public function getPriority() : int{ + return $this->priority; + } + + public static function decode(int $recipeType, NetworkBinaryStream $in) : self{ + $recipeId = $in->getString(); + $width = $in->getVarInt(); + $height = $in->getVarInt(); + $input = []; + for($row = 0; $row < $height; ++$row){ + for($column = 0; $column < $width; ++$column){ + $input[$row][$column] = $in->getRecipeIngredient(); + } + } + + $output = []; + for($k = 0, $resultCount = $in->getUnsignedVarInt(); $k < $resultCount; ++$k){ + $output[] = $in->getSlot(); + } + $uuid = $in->getUUID(); + $block = $in->getString(); + $priority = $in->getVarInt(); + + return new self($recipeType, $recipeId, $input, $output, $uuid, $block, $priority); + } + + public function encode(NetworkBinaryStream $out) : void{ + $out->putString($this->recipeId); + $out->putVarInt($this->getWidth()); + $out->putVarInt($this->getHeight()); + foreach($this->input as $row){ + foreach($row as $ingredient){ + $out->putRecipeIngredient($ingredient); + } + } + + $out->putUnsignedVarInt(count($this->output)); + foreach($this->output as $item){ + $out->putSlot($item); + } + + $out->putUUID($this->uuid); + $out->putString($this->blockName); + $out->putVarInt($this->priority); + } +} diff --git a/src/network/mcpe/protocol/types/recipe/ShapelessRecipe.php b/src/network/mcpe/protocol/types/recipe/ShapelessRecipe.php new file mode 100644 index 000000000..d91d1f4e6 --- /dev/null +++ b/src/network/mcpe/protocol/types/recipe/ShapelessRecipe.php @@ -0,0 +1,123 @@ +recipeId = $recipeId; + $this->inputs = $inputs; + $this->outputs = $outputs; + $this->uuid = $uuid; + $this->blockName = $blockName; + $this->priority = $priority; + } + + public function getRecipeId() : string{ + return $this->recipeId; + } + + /** + * @return RecipeIngredient[] + */ + public function getInputs() : array{ + return $this->inputs; + } + + /** + * @return ItemStack[] + */ + public function getOutputs() : array{ + return $this->outputs; + } + + public function getUuid() : UUID{ + return $this->uuid; + } + + public function getBlockName() : string{ + return $this->blockName; + } + + public function getPriority() : int{ + return $this->priority; + } + + public static function decode(int $recipeType, NetworkBinaryStream $in) : self{ + $recipeId = $in->getString(); + $input = []; + for($j = 0, $ingredientCount = $in->getUnsignedVarInt(); $j < $ingredientCount; ++$j){ + $input[] = $in->getRecipeIngredient(); + } + $output = []; + for($k = 0, $resultCount = $in->getUnsignedVarInt(); $k < $resultCount; ++$k){ + $output[] = $in->getSlot(); + } + $uuid = $in->getUUID(); + $block = $in->getString(); + $priority = $in->getVarInt(); + + return new self($recipeType, $recipeId, $input, $output, $uuid, $block, $priority); + } + + public function encode(NetworkBinaryStream $out) : void{ + $out->putString($this->recipeId); + $out->putUnsignedVarInt(count($this->inputs)); + foreach($this->inputs as $item){ + $out->putRecipeIngredient($item); + } + + $out->putUnsignedVarInt(count($this->outputs)); + foreach($this->outputs as $item){ + $out->putSlot($item); + } + + $out->putUUID($this->uuid); + $out->putString($this->blockName); + $out->putVarInt($this->priority); + } +} diff --git a/src/network/mcpe/serializer/NetworkBinaryStream.php b/src/network/mcpe/serializer/NetworkBinaryStream.php index 29de90a4d..4e0f2116a 100644 --- a/src/network/mcpe/serializer/NetworkBinaryStream.php +++ b/src/network/mcpe/serializer/NetworkBinaryStream.php @@ -25,14 +25,10 @@ namespace pocketmine\network\mcpe\serializer; #include -use pocketmine\item\Durable; -use pocketmine\item\Item; -use pocketmine\item\ItemFactory; use pocketmine\item\ItemIds; use pocketmine\math\Vector3; use pocketmine\nbt\NbtDataException; use pocketmine\nbt\tag\CompoundTag; -use pocketmine\nbt\tag\IntTag; use pocketmine\nbt\TreeRoot; use pocketmine\network\BadPacketException; use pocketmine\network\mcpe\protocol\types\command\CommandOriginData; @@ -48,8 +44,10 @@ use pocketmine\network\mcpe\protocol\types\entity\MetadataProperty; use pocketmine\network\mcpe\protocol\types\entity\ShortMetadataProperty; use pocketmine\network\mcpe\protocol\types\entity\StringMetadataProperty; use pocketmine\network\mcpe\protocol\types\entity\Vec3MetadataProperty; +use pocketmine\network\mcpe\protocol\types\inventory\ItemStack; use pocketmine\network\mcpe\protocol\types\PersonaPieceTintColor; use pocketmine\network\mcpe\protocol\types\PersonaSkinPiece; +use pocketmine\network\mcpe\protocol\types\recipe\RecipeIngredient; use pocketmine\network\mcpe\protocol\types\SkinAnimation; use pocketmine\network\mcpe\protocol\types\SkinData; use pocketmine\network\mcpe\protocol\types\SkinImage; @@ -63,9 +61,6 @@ use function strlen; class NetworkBinaryStream extends BinaryStream{ - private const DAMAGE_TAG = "Damage"; //TAG_Int - private const DAMAGE_TAG_CONFLICT_RESOLUTION = "___Damage_ProtocolCollisionResolution___"; - /** * @throws BinaryDataException */ @@ -209,15 +204,15 @@ class NetworkBinaryStream extends BinaryStream{ * @throws BadPacketException * @throws BinaryDataException */ - public function getSlot() : Item{ + public function getSlot() : ItemStack{ $id = $this->getVarInt(); if($id === 0){ - return ItemFactory::get(0, 0, 0); + return ItemStack::null(); } $auxValue = $this->getVarInt(); - $data = $auxValue >> 8; - $cnt = $auxValue & 0xff; + $meta = $auxValue >> 8; + $count = $auxValue & 0xff; $nbtLen = $this->getLShort(); @@ -237,43 +232,25 @@ class NetworkBinaryStream extends BinaryStream{ throw new BadPacketException("Unexpected fake NBT length $nbtLen"); } - //TODO - for($i = 0, $canPlaceOn = $this->getVarInt(); $i < $canPlaceOn; ++$i){ - $this->getString(); + $canPlaceOn = []; + for($i = 0, $canPlaceOnCount = $this->getVarInt(); $i < $canPlaceOnCount; ++$i){ + $canPlaceOn[] = $this->getString(); } - //TODO - for($i = 0, $canDestroy = $this->getVarInt(); $i < $canDestroy; ++$i){ - $this->getString(); + $canDestroy = []; + for($i = 0, $canDestroyCount = $this->getVarInt(); $i < $canDestroyCount; ++$i){ + $canDestroy[] = $this->getString(); } + $shieldBlockingTick = null; if($id === ItemIds::SHIELD){ - $this->getVarLong(); //"blocking tick" (ffs mojang) + $shieldBlockingTick = $this->getVarLong(); } - if($compound !== null){ - if($compound->hasTag(self::DAMAGE_TAG, IntTag::class)){ - $data = $compound->getInt(self::DAMAGE_TAG); - $compound->removeTag(self::DAMAGE_TAG); - if($compound->count() === 0){ - $compound = null; - goto end; - } - } - if(($conflicted = $compound->getTag(self::DAMAGE_TAG_CONFLICT_RESOLUTION)) !== null){ - $compound->removeTag(self::DAMAGE_TAG_CONFLICT_RESOLUTION); - $compound->setTag(self::DAMAGE_TAG, $conflicted); - } - } - end: - try{ - return ItemFactory::get($id, $data, $cnt, $compound); - }catch(\InvalidArgumentException $e){ - throw new BadPacketException($e->getMessage(), 0, $e); - } + return new ItemStack($id, $meta, $count, $compound, $canPlaceOn, $canDestroy, $shieldBlockingTick); } - public function putSlot(Item $item) : void{ + public function putSlot(ItemStack $item) : void{ if($item->getId() === 0){ $this->putVarInt(0); @@ -284,22 +261,7 @@ class NetworkBinaryStream extends BinaryStream{ $auxValue = (($item->getMeta() & 0x7fff) << 8) | $item->getCount(); $this->putVarInt($auxValue); - $nbt = null; - if($item->hasNamedTag()){ - $nbt = clone $item->getNamedTag(); - } - if($item instanceof Durable and $item->getDamage() > 0){ - if($nbt !== null){ - if(($existing = $nbt->getTag(self::DAMAGE_TAG)) !== null){ - $nbt->removeTag(self::DAMAGE_TAG); - $nbt->setTag(self::DAMAGE_TAG_CONFLICT_RESOLUTION, $existing); - } - }else{ - $nbt = new CompoundTag(); - } - $nbt->setInt(self::DAMAGE_TAG, $item->getDamage()); - } - + $nbt = $item->getNbt(); if($nbt !== null){ $this->putLShort(0xffff); $this->putByte(1); //TODO: some kind of count field? always 1 as of 1.9.0 @@ -308,34 +270,39 @@ class NetworkBinaryStream extends BinaryStream{ $this->putLShort(0); } - $this->putVarInt(0); //CanPlaceOn entry count (TODO) - $this->putVarInt(0); //CanDestroy entry count (TODO) + $this->putVarInt(count($item->getCanPlaceOn())); + foreach($item->getCanPlaceOn() as $entry){ + $this->putString($entry); + } + $this->putVarInt(count($item->getCanDestroy())); + foreach($item->getCanDestroy() as $entry){ + $this->putString($entry); + } - if($item->getId() === ItemIds::SHIELD){ - $this->putVarLong(0); //"blocking tick" (ffs mojang) + $blockingTick = $item->getShieldBlockingTick(); + if($blockingTick !== null){ + $this->putVarLong($blockingTick); } } - public function getRecipeIngredient() : Item{ + public function getRecipeIngredient() : RecipeIngredient{ $id = $this->getVarInt(); if($id === 0){ - return ItemFactory::get(ItemIds::AIR, 0, 0); + return new RecipeIngredient(0, 0, 0); } $meta = $this->getVarInt(); - if($meta === 0x7fff){ - $meta = -1; - } $count = $this->getVarInt(); - return ItemFactory::get($id, $meta, $count); + + return new RecipeIngredient($id, $meta, $count); } - public function putRecipeIngredient(Item $item) : void{ - if($item->isNull()){ + public function putRecipeIngredient(RecipeIngredient $ingredient) : void{ + if($ingredient->getId() === 0){ $this->putVarInt(0); }else{ - $this->putVarInt($item->getId()); - $this->putVarInt($item->getMeta() & 0x7fff); - $this->putVarInt($item->getCount()); + $this->putVarInt($ingredient->getId()); + $this->putVarInt($ingredient->getMeta()); + $this->putVarInt($ingredient->getCount()); } } diff --git a/src/world/particle/FloatingTextParticle.php b/src/world/particle/FloatingTextParticle.php index 8785eebfc..76d2c3f80 100644 --- a/src/world/particle/FloatingTextParticle.php +++ b/src/world/particle/FloatingTextParticle.php @@ -25,7 +25,6 @@ namespace pocketmine\world\particle; use pocketmine\entity\EntityFactory; use pocketmine\entity\Skin; -use pocketmine\item\ItemFactory; use pocketmine\math\Vector3; use pocketmine\network\mcpe\protocol\AddPlayerPacket; use pocketmine\network\mcpe\protocol\PlayerListPacket; @@ -34,6 +33,7 @@ use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataFlags; use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataProperties; use pocketmine\network\mcpe\protocol\types\entity\FloatMetadataProperty; use pocketmine\network\mcpe\protocol\types\entity\LongMetadataProperty; +use pocketmine\network\mcpe\protocol\types\inventory\ItemStack; use pocketmine\network\mcpe\protocol\types\PlayerListEntry; use pocketmine\network\mcpe\protocol\types\SkinAdapterSingleton; use pocketmine\utils\UUID; @@ -100,7 +100,7 @@ class FloatingTextParticle implements Particle{ $pk->username = $name; $pk->entityRuntimeId = $this->entityId; $pk->position = $pos; //TODO: check offset - $pk->item = ItemFactory::air(); + $pk->item = ItemStack::null(); $flags = ( 1 << EntityMetadataFlags::IMMOBILE