*/ private array $caches = []; /** * The client doesn't like recipes with ID 0 (as of 1.21.100) and complains about them in the content log * This doesn't actually affect the function of the recipe, but it is annoying, so this offset fixes it */ public const RECIPE_ID_OFFSET = 1; public function getCache(CraftingManager $manager) : CraftingDataPacket{ $id = spl_object_id($manager); if(!isset($this->caches[$id])){ $manager->getDestructorCallbacks()->add(function() use ($id) : void{ unset($this->caches[$id]); }); $manager->getRecipeRegisteredCallbacks()->add(function() use ($id) : void{ unset($this->caches[$id]); }); $this->caches[$id] = $this->buildCraftingDataCache($manager); } return $this->caches[$id]; } /** * Rebuilds the cached CraftingDataPacket. */ private function buildCraftingDataCache(CraftingManager $manager) : CraftingDataPacket{ Timings::$craftingDataCacheRebuild->startTiming(); $nullUUID = Uuid::fromString(Uuid::NIL); $converter = TypeConverter::getInstance(); $recipesWithTypeIds = []; $noUnlockingRequirement = new RecipeUnlockingRequirement(null); foreach($manager->getCraftingRecipeIndex() as $index => $recipe){ //the client doesn't like recipes with an ID of 0, so we need to offset them $recipeNetId = $index + self::RECIPE_ID_OFFSET; if($recipe instanceof ShapelessRecipe){ $typeTag = match($recipe->getType()){ ShapelessRecipeType::CRAFTING => CraftingRecipeBlockName::CRAFTING_TABLE, ShapelessRecipeType::STONECUTTER => CraftingRecipeBlockName::STONECUTTER, ShapelessRecipeType::CARTOGRAPHY => CraftingRecipeBlockName::CARTOGRAPHY_TABLE, ShapelessRecipeType::SMITHING => CraftingRecipeBlockName::SMITHING_TABLE, }; $recipesWithTypeIds[] = new ProtocolShapelessRecipe( CraftingDataPacket::ENTRY_SHAPELESS, Binary::writeInt($recipeNetId), array_map($converter->coreRecipeIngredientToNet(...), $recipe->getIngredientList()), array_map($converter->coreItemStackToNet(...), $recipe->getResults()), $nullUUID, $typeTag, 50, $noUnlockingRequirement, $recipeNetId ); }elseif($recipe instanceof ShapedRecipe){ $inputs = []; for($row = 0, $height = $recipe->getHeight(); $row < $height; ++$row){ for($column = 0, $width = $recipe->getWidth(); $column < $width; ++$column){ $inputs[$row][$column] = $converter->coreRecipeIngredientToNet($recipe->getIngredient($column, $row)); } } $recipesWithTypeIds[] = $r = new ProtocolShapedRecipe( CraftingDataPacket::ENTRY_SHAPED, Binary::writeInt($recipeNetId), $inputs, array_map($converter->coreItemStackToNet(...), $recipe->getResults()), $nullUUID, CraftingRecipeBlockName::CRAFTING_TABLE, 50, true, $noUnlockingRequirement, $recipeNetId, ); }else{ //TODO: probably special recipe types } } foreach(FurnaceType::cases() as $furnaceType){ $typeTag = match($furnaceType){ FurnaceType::FURNACE => FurnaceRecipeBlockName::FURNACE, FurnaceType::BLAST_FURNACE => FurnaceRecipeBlockName::BLAST_FURNACE, FurnaceType::SMOKER => FurnaceRecipeBlockName::SMOKER, FurnaceType::CAMPFIRE => FurnaceRecipeBlockName::CAMPFIRE, FurnaceType::SOUL_CAMPFIRE => FurnaceRecipeBlockName::SOUL_CAMPFIRE }; foreach($manager->getFurnaceRecipeManager($furnaceType)->getAll() as $recipe){ $input = $converter->coreRecipeIngredientToNet($recipe->getInput())->getDescriptor(); if(!$input instanceof IntIdMetaItemDescriptor){ throw new AssumptionFailedError(); } $recipesWithTypeIds[] = new ProtocolFurnaceRecipe( CraftingDataPacket::ENTRY_FURNACE_DATA, $input->getId(), $input->getMeta(), $converter->coreItemStackToNet($recipe->getResult()), $typeTag ); } } $potionTypeRecipes = []; foreach($manager->getPotionTypeRecipes() as $recipe){ $input = $converter->coreRecipeIngredientToNet($recipe->getInput())->getDescriptor(); $ingredient = $converter->coreRecipeIngredientToNet($recipe->getIngredient())->getDescriptor(); if(!$input instanceof IntIdMetaItemDescriptor || !$ingredient instanceof IntIdMetaItemDescriptor){ throw new AssumptionFailedError(); } $output = $converter->coreItemStackToNet($recipe->getOutput()); $potionTypeRecipes[] = new ProtocolPotionTypeRecipe( $input->getId(), $input->getMeta(), $ingredient->getId(), $ingredient->getMeta(), $output->getId(), $output->getMeta() ); } $potionContainerChangeRecipes = []; $itemTypeDictionary = $converter->getItemTypeDictionary(); foreach($manager->getPotionContainerChangeRecipes() as $recipe){ $input = $itemTypeDictionary->fromStringId($recipe->getInputItemId()); $ingredient = $converter->coreRecipeIngredientToNet($recipe->getIngredient())->getDescriptor(); if(!$ingredient instanceof IntIdMetaItemDescriptor){ throw new AssumptionFailedError(); } $output = $itemTypeDictionary->fromStringId($recipe->getOutputItemId()); $potionContainerChangeRecipes[] = new ProtocolPotionContainerChangeRecipe( $input, $ingredient->getId(), $output ); } Timings::$craftingDataCacheRebuild->stopTiming(); return CraftingDataPacket::create($recipesWithTypeIds, $potionTypeRecipes, $potionContainerChangeRecipes, [], true); } }