use pocketmine\inventory\FurnaceRecipe; use pocketmine\inventory\ShapedRecipe; use pocketmine\inventory\ShapelessRecipe; 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 use function count; use function str_repeat; class CraftingDataPacket extends DataPacket{ public const NETWORK_ID = ProtocolInfo::CRAFTING_DATA_PACKET; public const ENTRY_SHAPELESS = 0; 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 /** @var object[] */ public $entries = []; /** @var PotionTypeRecipe[] */ public $potionTypeRecipes = []; /** @var PotionContainerChangeRecipe[] */ public $potionContainerRecipes = []; /** @var bool */ public $cleanRecipes = false; public $decodedEntries = []; public function clean(){ $this->entries = []; $this->decodedEntries = []; return parent::clean(); } protected function decodePayload(){ $this->decodedEntries = []; $recipeCount = $this->getUnsignedVarInt(); for($i = 0; $i < $recipeCount; ++$i){ $entry = []; $entry["type"] = $recipeType = $this->getVarInt(); switch($recipeType){ case self::ENTRY_SHAPELESS: case self::ENTRY_SHULKER_BOX: case self::ENTRY_SHAPELESS_CHEMISTRY: $entry["recipe_id"] = $this->getString(); $ingredientCount = $this->getUnsignedVarInt(); /** @var Item */ $entry["input"] = []; for($j = 0; $j < $ingredientCount; ++$j){ $entry["input"][] = $in = $this->getRecipeIngredient(); $in->setCount(1); //TODO HACK: they send a useless count field which breaks the PM crafting system because it isn't always 1 } $resultCount = $this->getUnsignedVarInt(); $entry["output"] = []; for($k = 0; $k < $resultCount; ++$k){ $entry["output"][] = $this->getSlot(); } $entry["uuid"] = $this->getUUID()->toString(); $entry["block"] = $this->getString(); $entry["priority"] = $this->getVarInt(); break; case self::ENTRY_SHAPED: case self::ENTRY_SHAPED_CHEMISTRY: $entry["recipe_id"] = $this->getString(); $entry["width"] = $this->getVarInt(); $entry["height"] = $this->getVarInt(); $count = $entry["width"] * $entry["height"]; $entry["input"] = []; for($j = 0; $j < $count; ++$j){ $entry["input"][] = $in = $this->getRecipeIngredient(); $in->setCount(1); //TODO HACK: they send a useless count field which breaks the PM crafting system } $resultCount = $this->getUnsignedVarInt(); $entry["output"] = []; for($k = 0; $k < $resultCount; ++$k){ $entry["output"][] = $this->getSlot(); } $entry["uuid"] = $this->getUUID()->toString(); $entry["block"] = $this->getString(); $entry["priority"] = $this->getVarInt(); break; case self::ENTRY_FURNACE: case self::ENTRY_FURNACE_DATA: $inputId = $this->getVarInt(); $inputData = -1; if($recipeType === self::ENTRY_FURNACE_DATA){ $inputData = $this->getVarInt(); if($inputData === 0x7fff){ $inputData = -1; } } $entry["input"] = ItemFactory::get($inputId, $inputData); $entry["output"] = $out = $this->getSlot(); if($out->getDamage() === 0x7fff){ $out->setDamage(0); //TODO HACK: some 1.12 furnace recipe outputs have wildcard damage values } $entry["block"] = $this->getString(); break; case self::ENTRY_MULTI: $entry["uuid"] = $this->getUUID()->toString(); break; default: throw new \UnexpectedValueException("Unhandled recipe type $recipeType!"); //do not continue attempting to decode } $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(); } private static function writeEntry($entry, NetworkBinaryStream $stream, int $pos){ 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){ $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){ $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){ $stream->putVarInt($recipe->getInput()->getId()); $result = CraftingDataPacket::ENTRY_FURNACE; if(!$recipe->getInput()->hasAnyDamageValue()){ //Data recipe $stream->putVarInt($recipe->getInput()->getDamage()); $result = CraftingDataPacket::ENTRY_FURNACE_DATA; } $stream->putSlot($recipe->getResult()); $stream->putString("furnace"); //TODO: blocktype (no prefix) (this might require internal API breaks) return $result; } public function addShapelessRecipe(ShapelessRecipe $recipe){ $this->entries[] = $recipe; } public function addShapedRecipe(ShapedRecipe $recipe){ $this->entries[] = $recipe; } public function addFurnaceRecipe(FurnaceRecipe $recipe){ $this->entries[] = $recipe; } protected function encodePayload(){ $this->putUnsignedVarInt(count($this->entries)); $writer = new NetworkBinaryStream(); $counter = 0; foreach($this->entries as $d){ $entryType = self::writeEntry($d, $writer, $counter++); if($entryType >= 0){ $this->putVarInt($entryType); $this->put($writer->getBuffer()); }else{ $this->putVarInt(-1); } $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); } public function handle(NetworkSession $session) : bool{ return $session->handleCraftingData($this); } }