init(); } public function init() : void{ $recipes = json_decode(file_get_contents(\pocketmine\RESOURCE_PATH . "vanilla" . DIRECTORY_SEPARATOR . "recipes.json"), true); foreach($recipes as $recipe){ switch($recipe["type"]){ case 0: $this->registerRecipe(new ShapelessRecipe( array_map(function(array $data) : Item{ return Item::jsonDeserialize($data); }, $recipe["input"]), array_map(function(array $data) : Item{ return Item::jsonDeserialize($data); }, $recipe["output"]) )); break; case 1: $this->registerRecipe(new ShapedRecipe( $recipe["shape"], array_map(function(array $data) : Item{ return Item::jsonDeserialize($data); }, $recipe["input"]), array_map(function(array $data) : Item{ return Item::jsonDeserialize($data); }, $recipe["output"]) )); break; case 2: case 3: $result = $recipe["output"]; $resultItem = Item::jsonDeserialize($result); $this->registerRecipe(new FurnaceRecipe($resultItem, ItemFactory::get($recipe["inputId"], $recipe["inputDamage"] ?? -1, 1))); break; default: break; } } $this->buildCraftingDataCache(); } /** * Rebuilds the cached CraftingDataPacket. */ public function buildCraftingDataCache() : void{ Timings::$craftingDataCacheRebuildTimer->startTiming(); $pk = new CraftingDataPacket(); $pk->cleanRecipes = true; foreach($this->shapelessRecipes as $list){ foreach($list as $recipe){ $pk->addShapelessRecipe($recipe); } } foreach($this->shapedRecipes as $list){ foreach($list as $recipe){ $pk->addShapedRecipe($recipe); } } foreach($this->furnaceRecipes as $recipe){ $pk->addFurnaceRecipe($recipe); } $pk->encode(); $batch = new PacketStream(); $batch->putPacket($pk); $this->craftingDataCache = NetworkCompression::compress($batch->buffer); Timings::$craftingDataCacheRebuildTimer->stopTiming(); } /** * Returns a pre-compressed CraftingDataPacket for sending to players. Rebuilds the cache if it is not found. * * @return string */ public function getCraftingDataPacket() : string{ if($this->craftingDataCache === null){ $this->buildCraftingDataCache(); } return $this->craftingDataCache; } /** * Function used to arrange Shapeless Recipe ingredient lists into a consistent order. * * @param Item $i1 * @param Item $i2 * * @return int */ public static function sort(Item $i1, Item $i2){ //Use spaceship operator to compare each property, then try the next one if they are equivalent. ($retval = $i1->getId() <=> $i2->getId()) === 0 && ($retval = $i1->getDamage() <=> $i2->getDamage()) === 0 && ($retval = $i1->getCount() <=> $i2->getCount()); return $retval; } /** * @param Item[] $items * * @return Item[] */ private static function pack(array $items) : array{ /** @var Item[] $result */ $result = []; foreach($items as $i => $item){ foreach($result as $otherItem){ if($item->equals($otherItem)){ $otherItem->setCount($otherItem->getCount() + $item->getCount()); continue 2; } } //No matching item found $result[] = clone $item; } return $result; } private static function hashOutputs(array $outputs) : string{ $outputs = self::pack($outputs); usort($outputs, [self::class, "sort"]); foreach($outputs as $o){ //this reduces accuracy of hash, but it's necessary to deal with recipe book shift-clicking stupidity $o->setCount(1); } return json_encode($outputs); } /** * @return ShapelessRecipe[][] */ public function getShapelessRecipes() : array{ return $this->shapelessRecipes; } /** * @return ShapedRecipe[][] */ public function getShapedRecipes() : array{ return $this->shapedRecipes; } /** * @return FurnaceRecipe[] */ public function getFurnaceRecipes() : array{ return $this->furnaceRecipes; } /** * @param ShapedRecipe $recipe */ public function registerShapedRecipe(ShapedRecipe $recipe) : void{ $this->shapedRecipes[self::hashOutputs($recipe->getResults())][] = $recipe; $this->craftingDataCache = null; } /** * @param ShapelessRecipe $recipe */ public function registerShapelessRecipe(ShapelessRecipe $recipe) : void{ $this->shapelessRecipes[self::hashOutputs($recipe->getResults())][] = $recipe; $this->craftingDataCache = null; } /** * @param FurnaceRecipe $recipe */ public function registerFurnaceRecipe(FurnaceRecipe $recipe) : void{ $input = $recipe->getInput(); $this->furnaceRecipes[$input->getId() . ":" . ($input->hasAnyDamageValue() ? "?" : $input->getDamage())] = $recipe; $this->craftingDataCache = null; } /** * @param CraftingGrid $grid * @param Item[] $outputs * * @return CraftingRecipe|null */ public function matchRecipe(CraftingGrid $grid, array $outputs) : ?CraftingRecipe{ //TODO: try to match special recipes before anything else (first they need to be implemented!) $outputHash = self::hashOutputs($outputs); if(isset($this->shapedRecipes[$outputHash])){ foreach($this->shapedRecipes[$outputHash] as $recipe){ if($recipe->matchesCraftingGrid($grid)){ return $recipe; } } } if(isset($this->shapelessRecipes[$outputHash])){ foreach($this->shapelessRecipes[$outputHash] as $recipe){ if($recipe->matchesCraftingGrid($grid)){ return $recipe; } } } return null; } /** * @param Item[] $outputs * * @return CraftingRecipe[]|\Generator */ public function matchRecipeByOutputs(array $outputs) : \Generator{ //TODO: try to match special recipes before anything else (first they need to be implemented!) $outputHash = self::hashOutputs($outputs); if(isset($this->shapedRecipes[$outputHash])){ foreach($this->shapedRecipes[$outputHash] as $recipe){ yield $recipe; } } if(isset($this->shapelessRecipes[$outputHash])){ foreach($this->shapelessRecipes[$outputHash] as $recipe){ yield $recipe; } } } /** * @param Item $input * * @return FurnaceRecipe|null */ public function matchFurnaceRecipe(Item $input) : ?FurnaceRecipe{ return $this->furnaceRecipes[$input->getId() . ":" . $input->getDamage()] ?? $this->furnaceRecipes[$input->getId() . ":?"] ?? null; } /** * @param Recipe $recipe */ public function registerRecipe(Recipe $recipe) : void{ $recipe->registerToCraftingManager($this); } }