diff --git a/src/crafting/CraftingManagerFromDataHelper.php b/src/crafting/CraftingManagerFromDataHelper.php index c07182523..7e5362d22 100644 --- a/src/crafting/CraftingManagerFromDataHelper.php +++ b/src/crafting/CraftingManagerFromDataHelper.php @@ -52,14 +52,14 @@ use function json_decode; final class CraftingManagerFromDataHelper{ private static function deserializeIngredient(RecipeIngredientData $data) : ?RecipeIngredient{ - if(!isset($data->name)){ - return null; //TODO: not yet implemented - } if(isset($data->count) && $data->count !== 1){ //every case we've seen so far where this isn't the case, it's been a bug and the count was ignored anyway //e.g. gold blocks crafted from 9 ingots, but each input item individually had a count of 9 throw new SavedDataLoadingException("Recipe inputs should have a count of exactly 1"); } + if(isset($data->tag)){ + return new TagWildcardRecipeIngredient($data->tag); + } $meta = $data->meta ?? null; if($meta === RecipeIngredientData::WILDCARD_META_VALUE){ diff --git a/src/crafting/TagWildcardRecipeIngredient.php b/src/crafting/TagWildcardRecipeIngredient.php new file mode 100644 index 000000000..7ac261f08 --- /dev/null +++ b/src/crafting/TagWildcardRecipeIngredient.php @@ -0,0 +1,55 @@ +tagName; } + + public function accepts(Item $item) : bool{ + if($item->getCount() < 1){ + return false; + } + + return ItemTagToIdMap::getInstance()->tagContainsId($this->tagName, GlobalItemDataHandlers::getSerializer()->serializeType($item)->getName()); + } + + public function __toString() : string{ + return "TagWildcardRecipeIngredient($this->tagName)"; + } +} \ No newline at end of file diff --git a/src/data/bedrock/ItemTagToIdMap.php b/src/data/bedrock/ItemTagToIdMap.php new file mode 100644 index 000000000..a5fb7952c --- /dev/null +++ b/src/data/bedrock/ItemTagToIdMap.php @@ -0,0 +1,102 @@ + file_get_contents(Path::join(BEDROCK_DATA_PATH, 'item_tags.json'))), true, flags: JSON_THROW_ON_ERROR); + if(!is_array($map)){ + throw new AssumptionFailedError("Invalid item tag map, expected array"); + } + $cleanMap = []; + foreach($map as $tagName => $ids){ + if(!is_string($tagName)){ + throw new AssumptionFailedError("Invalid item tag name $tagName, expected string as key"); + } + if(!is_array($ids)){ + throw new AssumptionFailedError("Invalid item tag $tagName, expected array of IDs as value"); + } + $cleanIds = []; + foreach($ids as $id){ + if(!is_string($id)){ + throw new AssumptionFailedError("Invalid item tag $tagName, expected string as ID, got " . gettype($id)); + } + $cleanIds[] = $id; + } + $cleanMap[$tagName] = $cleanIds; + } + + return new self($cleanMap); + } + + /** + * @var true[][] + * @phpstan-var array> + */ + private array $tagToIdsMap = []; + + /** + * @param string[][] $tagToIds + * @phpstan-param array> $tagToIds + */ + public function __construct( + array $tagToIds + ){ + foreach(Utils::stringifyKeys($tagToIds) as $tag => $ids){ + foreach($ids as $id){ + $this->tagToIdsMap[$tag][$id] = true; + } + } + } + + /** + * @return string[] + * @phpstan-return list + */ + public function getIdsForTag(string $tag) : array{ + return array_keys($this->tagToIdsMap[$tag] ?? []); + } + + public function tagContainsId(string $tag, string $id) : bool{ + return isset($this->tagToIdsMap[$tag][$id]); + } + + public function addIdToTag(string $tag, string $id) : void{ + $this->tagToIdsMap[$tag][$id] = true; + } +} \ No newline at end of file diff --git a/src/network/mcpe/convert/TypeConverter.php b/src/network/mcpe/convert/TypeConverter.php index 26b4a2ddf..27836a3c7 100644 --- a/src/network/mcpe/convert/TypeConverter.php +++ b/src/network/mcpe/convert/TypeConverter.php @@ -27,6 +27,7 @@ use pocketmine\block\VanillaBlocks; use pocketmine\crafting\ExactRecipeIngredient; use pocketmine\crafting\MetaWildcardRecipeIngredient; use pocketmine\crafting\RecipeIngredient; +use pocketmine\crafting\TagWildcardRecipeIngredient; use pocketmine\data\bedrock\item\BlockItemIdMap; use pocketmine\inventory\transaction\action\CreateItemAction; use pocketmine\inventory\transaction\action\DestroyItemAction; @@ -46,6 +47,7 @@ use pocketmine\network\mcpe\protocol\types\inventory\UIInventorySlotOffset; use pocketmine\network\mcpe\protocol\types\recipe\IntIdMetaItemDescriptor; use pocketmine\network\mcpe\protocol\types\recipe\RecipeIngredient as ProtocolRecipeIngredient; use pocketmine\network\mcpe\protocol\types\recipe\StringIdMetaItemDescriptor; +use pocketmine\network\mcpe\protocol\types\recipe\TagItemDescriptor; use pocketmine\player\GameMode; use pocketmine\player\Player; use pocketmine\utils\AssumptionFailedError; @@ -118,6 +120,7 @@ class TypeConverter{ if($ingredient instanceof MetaWildcardRecipeIngredient){ $id = GlobalItemTypeDictionary::getInstance()->getDictionary()->fromStringId($ingredient->getItemId()); $meta = self::RECIPE_INPUT_WILDCARD_META; + $descriptor = new IntIdMetaItemDescriptor($id, $meta); }elseif($ingredient instanceof ExactRecipeIngredient){ $item = $ingredient->getItem(); [$id, $meta, $blockRuntimeId] = ItemTranslator::getInstance()->toNetworkId($item); @@ -127,11 +130,14 @@ class TypeConverter{ throw new AssumptionFailedError("Every block state should have an associated meta value"); } } + $descriptor = new IntIdMetaItemDescriptor($id, $meta); + }elseif($ingredient instanceof TagWildcardRecipeIngredient){ + $descriptor = new TagItemDescriptor($ingredient->getTagName()); }else{ throw new \LogicException("Unsupported recipe ingredient type " . get_class($ingredient) . ", only " . ExactRecipeIngredient::class . " and " . MetaWildcardRecipeIngredient::class . " are supported"); } - return new ProtocolRecipeIngredient(new IntIdMetaItemDescriptor($id, $meta), 1); + return new ProtocolRecipeIngredient($descriptor, 1); } public function netRecipeIngredientToCore(ProtocolRecipeIngredient $ingredient) : ?RecipeIngredient{ @@ -140,6 +146,10 @@ class TypeConverter{ return null; } + if($descriptor instanceof TagItemDescriptor){ + return new TagWildcardRecipeIngredient($descriptor->getTag()); + } + if($descriptor instanceof IntIdMetaItemDescriptor){ $stringId = GlobalItemTypeDictionary::getInstance()->getDictionary()->fromIntId($descriptor->getId()); $meta = $descriptor->getMeta();