From 9f0b32e748e4052a4f81d841817d15adca5a05bd Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 4 Jul 2022 20:28:07 +0100 Subject: [PATCH] Updated creative and crafting data to 1.19 --- composer.lock | 8 +- src/Server.php | 2 +- src/crafting/CraftingManager.php | 4 +- .../CraftingManagerFromDataHelper.php | 280 +++++++++++++----- src/crafting/PotionContainerChangeRecipe.php | 10 +- src/crafting/PotionTypeRecipe.php | 16 +- src/crafting/json/FurnaceRecipeData.php | 42 +++ src/crafting/json/ItemStackData.php | 43 +++ .../json/PotionContainerChangeRecipeData.php | 41 +++ src/crafting/json/PotionTypeRecipeData.php | 41 +++ src/crafting/json/RecipeIngredientData.php | 40 +++ src/crafting/json/ShapedRecipeData.php | 72 +++++ src/crafting/json/ShapelessRecipeData.php | 61 ++++ src/data/bedrock/item/BlockItemIdMap.php | 17 +- src/inventory/CreativeInventory.php | 23 +- src/network/mcpe/cache/CraftingDataCache.php | 6 +- tests/phpstan/configs/actual-problems.neon | 15 - 17 files changed, 592 insertions(+), 129 deletions(-) create mode 100644 src/crafting/json/FurnaceRecipeData.php create mode 100644 src/crafting/json/ItemStackData.php create mode 100644 src/crafting/json/PotionContainerChangeRecipeData.php create mode 100644 src/crafting/json/PotionTypeRecipeData.php create mode 100644 src/crafting/json/RecipeIngredientData.php create mode 100644 src/crafting/json/ShapedRecipeData.php create mode 100644 src/crafting/json/ShapelessRecipeData.php diff --git a/composer.lock b/composer.lock index fb0e297d2..d946ebef0 100644 --- a/composer.lock +++ b/composer.lock @@ -280,12 +280,12 @@ "source": { "type": "git", "url": "https://github.com/pmmp/BedrockData.git", - "reference": "a546e15f6a8d7498fb25d5a02ce16184a429bb78" + "reference": "01948a627448395d9946c2e6a5e8332a8456eaa0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/BedrockData/zipball/a546e15f6a8d7498fb25d5a02ce16184a429bb78", - "reference": "a546e15f6a8d7498fb25d5a02ce16184a429bb78", + "url": "https://api.github.com/repos/pmmp/BedrockData/zipball/01948a627448395d9946c2e6a5e8332a8456eaa0", + "reference": "01948a627448395d9946c2e6a5e8332a8456eaa0", "shasum": "" }, "type": "library", @@ -298,7 +298,7 @@ "issues": "https://github.com/pmmp/BedrockData/issues", "source": "https://github.com/pmmp/BedrockData/tree/modern-world-support" }, - "time": "2022-07-02T15:28:28+00:00" + "time": "2022-07-04T16:59:39+00:00" }, { "name": "pocketmine/bedrock-item-upgrade-schema", diff --git a/src/Server.php b/src/Server.php index 3128a7c39..66bcc1c66 100644 --- a/src/Server.php +++ b/src/Server.php @@ -963,7 +963,7 @@ class Server{ $this->commandMap = new SimpleCommandMap($this); - $this->craftingManager = CraftingManagerFromDataHelper::make(Path::join(\pocketmine\BEDROCK_DATA_PATH, "recipes.json")); + $this->craftingManager = CraftingManagerFromDataHelper::make(Path::join(\pocketmine\BEDROCK_DATA_PATH, "recipes")); $this->resourceManager = new ResourcePackManager(Path::join($this->getDataPath(), "resource_packs"), $this->logger); diff --git a/src/crafting/CraftingManager.php b/src/crafting/CraftingManager.php index 777776f1f..42e0db705 100644 --- a/src/crafting/CraftingManager.php +++ b/src/crafting/CraftingManager.php @@ -264,13 +264,13 @@ class CraftingManager{ } foreach($this->potionContainerChangeRecipes as $recipe){ - if($recipe->getIngredient()->equals($ingredient) && $recipe->getResultFor($input) !== null){ + if($recipe->getIngredient()->accepts($ingredient) && $recipe->getResultFor($input) !== null){ return $this->brewingRecipeCache[$inputHash][$ingredientHash] = $recipe; } } foreach($this->potionTypeRecipes as $recipe){ - if($recipe->getIngredient()->equals($ingredient) && $recipe->getResultFor($input) !== null){ + if($recipe->getIngredient()->accepts($ingredient) && $recipe->getResultFor($input) !== null){ return $this->brewingRecipeCache[$inputHash][$ingredientHash] = $recipe; } } diff --git a/src/crafting/CraftingManagerFromDataHelper.php b/src/crafting/CraftingManagerFromDataHelper.php index cb639209a..9767bb5ff 100644 --- a/src/crafting/CraftingManagerFromDataHelper.php +++ b/src/crafting/CraftingManagerFromDataHelper.php @@ -23,61 +23,205 @@ declare(strict_types=1); namespace pocketmine\crafting; +use pocketmine\crafting\json\FurnaceRecipeData; +use pocketmine\crafting\json\ItemStackData; +use pocketmine\crafting\json\PotionContainerChangeRecipeData; +use pocketmine\crafting\json\PotionTypeRecipeData; +use pocketmine\crafting\json\RecipeIngredientData; +use pocketmine\crafting\json\ShapedRecipeData; +use pocketmine\crafting\json\ShapelessRecipeData; +use pocketmine\data\bedrock\block\BlockStateData; +use pocketmine\data\bedrock\item\BlockItemIdMap; use pocketmine\data\bedrock\item\ItemTypeDeserializeException; -use pocketmine\data\bedrock\item\upgrade\LegacyItemIdToStringIdMap; +use pocketmine\data\bedrock\item\SavedItemData; +use pocketmine\data\bedrock\item\SavedItemStackData; use pocketmine\data\SavedDataLoadingException; +use pocketmine\errorhandler\ErrorToExceptionHandler; use pocketmine\item\Item; +use pocketmine\nbt\LittleEndianNbtSerializer; +use pocketmine\nbt\tag\CompoundTag; +use pocketmine\network\mcpe\convert\RuntimeBlockMapping; use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\Utils; use pocketmine\world\format\io\GlobalItemDataHandlers; -use function array_map; +use Webmozart\PathUtil\Path; +use function base64_decode; use function file_get_contents; +use function get_debug_type; use function is_array; -use function is_int; +use function is_object; use function json_decode; final class CraftingManagerFromDataHelper{ - /** - * @param mixed[] $data - */ - private static function deserializeIngredient(array $data) : ?RecipeIngredient{ - if(!isset($data["id"]) || !is_int($data["id"])){ - throw new \InvalidArgumentException("Invalid input data, expected int ID"); - } - if(isset($data["damage"]) && $data["damage"] === -1){ - try{ - $typeData = GlobalItemDataHandlers::getUpgrader()->upgradeItemTypeDataInt($data["id"], 0, 1, null); - }catch(ItemTypeDeserializeException){ - //probably unknown item - return null; + + private static function deserializeItemStackFromNameMeta(string $name, int $meta) : ?Item{ + $blockName = BlockItemIdMap::getInstance()->lookupBlockId($name); + if($blockName !== null){ + $blockStateDictionary = RuntimeBlockMapping::getInstance()->getBlockStateDictionary(); + $blockRuntimeId = $blockStateDictionary->lookupStateIdFromIdMeta($name, $meta === RecipeIngredientData::WILDCARD_META_VALUE ? 0 : $meta); + if($blockRuntimeId === null){ + throw new \InvalidArgumentException("$blockName with meta $meta doesn't map to any known blockstate"); } - - return new MetaWildcardRecipeIngredient($typeData->getTypeData()->getName()); + $blockStateData = $blockStateDictionary->getDataFromStateId($blockRuntimeId); + if($blockStateData === null){ + throw new AssumptionFailedError("We just looked up the runtime ID for this state, so it can't possibly be null"); + } + }else{ + $blockStateData = null; } - //TODO: we need to stop using jsonDeserialize for this + //TODO: for wildcards, we only need a way to check if the item serializer recognizes the ID; we don't need to + //deserialize the whole itemstack, which might give bogus results anyway if meta 0 isn't recognized + $itemTypeData = new SavedItemData( + $name, + $meta === RecipeIngredientData::WILDCARD_META_VALUE ? 0 : $meta, + $blockStateData, + null + ); + try{ - $item = Item::legacyJsonDeserialize($data); - }catch(SavedDataLoadingException){ - //unknown item + return GlobalItemDataHandlers::getDeserializer()->deserializeType($itemTypeData); + }catch(ItemTypeDeserializeException){ + //probably unknown item return null; } - - return new ExactRecipeIngredient($item); } - public static function make(string $filePath) : CraftingManager{ - $recipes = json_decode(Utils::assumeNotFalse(file_get_contents($filePath), "Missing required resource file"), true); - if(!is_array($recipes)){ - throw new AssumptionFailedError("recipes.json root should contain a map of recipe types"); + private static function deserializeIngredient(RecipeIngredientData $data) : ?RecipeIngredient{ + 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"); } + + $itemStack = self::deserializeItemStackFromNameMeta($data->name, $data->meta); + if($itemStack === null){ + //probably unknown item + return null; + } + return $data->meta === RecipeIngredientData::WILDCARD_META_VALUE ? + new MetaWildcardRecipeIngredient($data->name) : + new ExactRecipeIngredient($itemStack); + } + + public static function deserializeItemStack(ItemStackData $data) : ?Item{ + //count, name, block_name, block_states, meta, nbt, can_place_on, can_destroy + $name = $data->name; + $meta = $data->meta ?? 0; + $count = $data->count ?? 1; + + $blockStatesRaw = $data->block_states ?? null; + $nbtRaw = $data->nbt ?? null; + $canPlaceOn = $data->can_place_on ?? []; + $canDestroy = $data->can_destroy ?? []; + + $blockName = BlockItemIdMap::getInstance()->lookupBlockId($name); + if($blockName !== null){ + if($meta !== 0){ + throw new \InvalidArgumentException("Meta should not be specified for blockitems"); + } + $blockStatesTag = $blockStatesRaw === null ? + CompoundTag::create() : + (new LittleEndianNbtSerializer()) + ->read(ErrorToExceptionHandler::trapAndRemoveFalse(fn() => base64_decode($blockStatesRaw, true))) + ->mustGetCompoundTag(); + $blockStateData = new BlockStateData($blockName, $blockStatesTag, BlockStateData::CURRENT_VERSION); + }else{ + $blockStateData = null; + } + + $nbt = $nbtRaw === null ? null : (new LittleEndianNbtSerializer()) + ->read(ErrorToExceptionHandler::trapAndRemoveFalse(fn() => base64_decode($nbtRaw, true))) + ->mustGetCompoundTag(); + + $itemStackData = new SavedItemStackData( + new SavedItemData( + $name, + $meta, + $blockStateData, + $nbt + ), + $count, + null, + null, + $canPlaceOn, + $canDestroy, + ); + + try{ + return GlobalItemDataHandlers::getDeserializer()->deserializeStack($itemStackData); + }catch(ItemTypeDeserializeException){ + //probably unknown item + return null; + } + } + + /** + * @return mixed[] + * + * @phpstan-template TData of object + * @phpstan-param class-string $modelCLass + * @phpstan-return list + */ + public static function loadJsonArrayOfObjectsFile(string $filePath, string $modelCLass) : array{ + $recipes = json_decode(Utils::assumeNotFalse(file_get_contents($filePath), "Missing required resource file")); + if(!is_array($recipes)){ + throw new AssumptionFailedError("$filePath root should be an array, got " . get_debug_type($recipes)); + } + + $mapper = new \JsonMapper(); + $mapper->bStrictObjectTypeChecking = true; + $mapper->bExceptionOnUndefinedProperty = true; + $mapper->bExceptionOnMissingData = true; + + return self::loadJsonObjectListIntoModel($mapper, $modelCLass, $recipes); + } + + /** + * @phpstan-template TRecipeData of object + * @phpstan-param class-string $modelClass + * @phpstan-return TRecipeData + */ + private static function loadJsonObjectIntoModel(\JsonMapper $mapper, string $modelClass, object $data) : object{ + //JsonMapper does this for subtypes, but not for the base type :( + try{ + return $mapper->map($data, (new \ReflectionClass($modelClass))->newInstanceWithoutConstructor()); + }catch(\JsonMapper_Exception $e){ + throw new SavedDataLoadingException($e->getMessage(), 0, $e); + } + } + + /** + * @param mixed[] $data + * @return object[] + * + * @phpstan-template TRecipeData of object + * @phpstan-param class-string $modelClass + * @phpstan-return list + */ + private static function loadJsonObjectListIntoModel(\JsonMapper $mapper, string $modelClass, array $data) : array{ + $result = []; + foreach($data as $i => $item){ + if(!is_object($item)){ + throw new SavedDataLoadingException("Invalid entry at index $i: expected object, got " . get_debug_type($item)); + } + try{ + $result[] = self::loadJsonObjectIntoModel($mapper, $modelClass, $item); + }catch(SavedDataLoadingException $e){ + throw new SavedDataLoadingException("Invalid entry at index $i: " . $e->getMessage(), 0, $e); + } + } + return $result; + } + + public static function make(string $directoryPath) : CraftingManager{ $result = new CraftingManager(); $ingredientDeserializerFunc = \Closure::fromCallable([self::class, "deserializeIngredient"]); - $itemDeserializerFunc = \Closure::fromCallable([Item::class, 'legacyJsonDeserialize']); + $itemDeserializerFunc = \Closure::fromCallable([self::class, 'deserializeItemStack']); - foreach($recipes["shapeless"] as $recipe){ - $recipeType = match($recipe["block"]){ + foreach(self::loadJsonArrayOfObjectsFile(Path::join($directoryPath, 'shapeless_crafting.json'), ShapelessRecipeData::class) as $recipe){ + $recipeType = match($recipe->block){ "crafting_table" => ShapelessRecipeType::CRAFTING(), "stonecutter" => ShapelessRecipeType::STONECUTTER(), //TODO: Cartography Table @@ -87,18 +231,20 @@ final class CraftingManagerFromDataHelper{ continue; } $inputs = []; - foreach($recipe["input"] as $inputData){ + foreach($recipe->input as $inputData){ $input = $ingredientDeserializerFunc($inputData); if($input === null){ //unknown input item continue 2; } $inputs[] = $input; } - try{ - $outputs = array_map($itemDeserializerFunc, $recipe["output"]); - }catch(SavedDataLoadingException){ - //unknown output item - continue; + $outputs = []; + foreach($recipe->output as $outputData){ + $output = $itemDeserializerFunc($outputData); + if($output === null){ //unknown output item + continue 2; + } + $outputs[] = $output; } $result->registerShapelessRecipe(new ShapelessRecipe( $inputs, @@ -106,32 +252,34 @@ final class CraftingManagerFromDataHelper{ $recipeType )); } - foreach($recipes["shaped"] as $recipe){ - if($recipe["block"] !== "crafting_table"){ //TODO: filter others out for now to avoid breaking economics + foreach(self::loadJsonArrayOfObjectsFile(Path::join($directoryPath, 'shaped_crafting.json'), ShapedRecipeData::class) as $recipe){ + if($recipe->block !== "crafting_table"){ //TODO: filter others out for now to avoid breaking economics continue; } $inputs = []; - foreach($recipe["input"] as $symbol => $inputData){ + foreach(Utils::stringifyKeys($recipe->input) as $symbol => $inputData){ $input = $ingredientDeserializerFunc($inputData); if($input === null){ //unknown input item continue 2; } $inputs[$symbol] = $input; } - try{ - $outputs = array_map($itemDeserializerFunc, $recipe["output"]); - }catch(SavedDataLoadingException){ - //unknown output item - continue; + $outputs = []; + foreach($recipe->output as $outputData){ + $output = $itemDeserializerFunc($outputData); + if($output === null){ //unknown output item + continue 2; + } + $outputs[] = $output; } $result->registerShapedRecipe(new ShapedRecipe( - $recipe["shape"], + $recipe->shape, $inputs, $outputs )); } - foreach($recipes["smelting"] as $recipe){ - $furnaceType = match ($recipe["block"]){ + foreach(self::loadJsonArrayOfObjectsFile(Path::join($directoryPath, 'smelting.json'), FurnaceRecipeData::class) as $recipe){ + $furnaceType = match ($recipe->block){ "furnace" => FurnaceType::FURNACE(), "blast_furnace" => FurnaceType::BLAST_FURNACE(), "smoker" => FurnaceType::SMOKER(), @@ -141,12 +289,11 @@ final class CraftingManagerFromDataHelper{ if($furnaceType === null){ continue; } - try{ - $output = Item::legacyJsonDeserialize($recipe["output"]); - }catch(SavedDataLoadingException){ + $output = self::deserializeItemStack($recipe->output); + if($output === null){ continue; } - $input = self::deserializeIngredient($recipe["input"]); + $input = self::deserializeIngredient($recipe->input); if($input === null){ continue; } @@ -155,13 +302,12 @@ final class CraftingManagerFromDataHelper{ $input )); } - foreach($recipes["potion_type"] as $recipe){ - try{ - $input = Item::legacyJsonDeserialize($recipe["input"]); - $ingredient = Item::legacyJsonDeserialize($recipe["ingredient"]); - $output = Item::legacyJsonDeserialize($recipe["output"]); - }catch(SavedDataLoadingException){ - //unknown item + + foreach(self::loadJsonArrayOfObjectsFile(Path::join($directoryPath, 'potion_type.json'), PotionTypeRecipeData::class) as $recipe){ + $input = self::deserializeIngredient($recipe->input); + $ingredient = self::deserializeIngredient($recipe->ingredient); + $output = self::deserializeItemStack($recipe->output); + if($input === null || $ingredient === null || $output === null){ continue; } $result->registerPotionTypeRecipe(new PotionTypeRecipe( @@ -170,18 +316,16 @@ final class CraftingManagerFromDataHelper{ $output )); } - foreach($recipes["potion_container_change"] as $recipe){ - try{ - $ingredient = Item::legacyJsonDeserialize($recipe["ingredient"]); - }catch(SavedDataLoadingException){ - //unknown item + foreach(self::loadJsonArrayOfObjectsFile(Path::join($directoryPath, 'potion_container_change.json'), PotionContainerChangeRecipeData::class) as $recipe){ + $ingredient = self::deserializeIngredient($recipe->ingredient); + if($ingredient === null){ continue; } - //TODO: we'll be able to get rid of these conversions once the crafting data is updated - $inputId = LegacyItemIdToStringIdMap::getInstance()->legacyToString($recipe["input_item_id"]); - $outputId = LegacyItemIdToStringIdMap::getInstance()->legacyToString($recipe["output_item_id"]); - if($inputId === null || $outputId === null){ + $inputId = $recipe->input_item_name; + $outputId = $recipe->output_item_name; + + if(self::deserializeItemStackFromNameMeta($inputId, 0) === null || self::deserializeItemStackFromNameMeta($outputId, 0) === null){ //unknown item continue; } diff --git a/src/crafting/PotionContainerChangeRecipe.php b/src/crafting/PotionContainerChangeRecipe.php index d358c28e2..3530aee68 100644 --- a/src/crafting/PotionContainerChangeRecipe.php +++ b/src/crafting/PotionContainerChangeRecipe.php @@ -31,18 +31,16 @@ class PotionContainerChangeRecipe implements BrewingRecipe{ public function __construct( private string $inputItemId, - private Item $ingredient, + private RecipeIngredient $ingredient, private string $outputItemId - ){ - $this->ingredient = clone $ingredient; - } + ){} public function getInputItemId() : string{ return $this->inputItemId; } - public function getIngredient() : Item{ - return clone $this->ingredient; + public function getIngredient() : RecipeIngredient{ + return $this->ingredient; } public function getOutputItemId() : string{ diff --git a/src/crafting/PotionTypeRecipe.php b/src/crafting/PotionTypeRecipe.php index 4d450450e..1d0b1b580 100644 --- a/src/crafting/PotionTypeRecipe.php +++ b/src/crafting/PotionTypeRecipe.php @@ -28,21 +28,19 @@ use pocketmine\item\Item; class PotionTypeRecipe implements BrewingRecipe{ public function __construct( - private Item $input, - private Item $ingredient, + private RecipeIngredient $input, + private RecipeIngredient $ingredient, private Item $output ){ - $this->input = clone $input; - $this->ingredient = clone $ingredient; $this->output = clone $output; } - public function getInput() : Item{ - return clone $this->input; + public function getInput() : RecipeIngredient{ + return $this->input; } - public function getIngredient() : Item{ - return clone $this->ingredient; + public function getIngredient() : RecipeIngredient{ + return $this->ingredient; } public function getOutput() : Item{ @@ -50,6 +48,6 @@ class PotionTypeRecipe implements BrewingRecipe{ } public function getResultFor(Item $input) : ?Item{ - return $input->equals($this->input, true, false) ? $this->getOutput() : null; + return $this->input->accepts($input) ? $this->getOutput() : null; } } diff --git a/src/crafting/json/FurnaceRecipeData.php b/src/crafting/json/FurnaceRecipeData.php new file mode 100644 index 000000000..c5513030b --- /dev/null +++ b/src/crafting/json/FurnaceRecipeData.php @@ -0,0 +1,42 @@ +input = $input; + $this->output = $output; + $this->block = $block; + } +} diff --git a/src/crafting/json/ItemStackData.php b/src/crafting/json/ItemStackData.php new file mode 100644 index 000000000..032c7da7d --- /dev/null +++ b/src/crafting/json/ItemStackData.php @@ -0,0 +1,43 @@ +name = $name; + } +} diff --git a/src/crafting/json/PotionContainerChangeRecipeData.php b/src/crafting/json/PotionContainerChangeRecipeData.php new file mode 100644 index 000000000..4664a0ae9 --- /dev/null +++ b/src/crafting/json/PotionContainerChangeRecipeData.php @@ -0,0 +1,41 @@ +input_item_name = $input_item_name; + $this->ingredient = $ingredient; + $this->output_item_name = $output_item_name; + } +} diff --git a/src/crafting/json/PotionTypeRecipeData.php b/src/crafting/json/PotionTypeRecipeData.php new file mode 100644 index 000000000..d50005893 --- /dev/null +++ b/src/crafting/json/PotionTypeRecipeData.php @@ -0,0 +1,41 @@ +input = $input; + $this->ingredient = $ingredient; + $this->output = $output; + } +} diff --git a/src/crafting/json/RecipeIngredientData.php b/src/crafting/json/RecipeIngredientData.php new file mode 100644 index 000000000..ece2a6f62 --- /dev/null +++ b/src/crafting/json/RecipeIngredientData.php @@ -0,0 +1,40 @@ +name = $name; + $this->meta = $meta; + } +} diff --git a/src/crafting/json/ShapedRecipeData.php b/src/crafting/json/ShapedRecipeData.php new file mode 100644 index 000000000..abf37098e --- /dev/null +++ b/src/crafting/json/ShapedRecipeData.php @@ -0,0 +1,72 @@ + + */ + public array $shape; + + /** + * @required + * @var RecipeIngredientData[] + * @phpstan-var array + */ + public array $input; + + /** + * @required + * @var ItemStackData[] + * @phpstan-var list + */ + public array $output; + + /** @required */ + public string $block; + + /** @required */ + public int $priority; + + /** + * TODO: convert this to use promoted properties - avoiding them for now since it would break JsonMapper + * + * @param string[] $shape + * @param RecipeIngredientData[] $input + * @param ItemStackData[] $output + * + * @phpstan-param list $shape + * @phpstan-param array $input + * @phpstan-param list $output + */ + public function __construct(array $shape, array $input, array $output, string $block, int $priority){ + $this->block = $block; + $this->priority = $priority; + $this->shape = $shape; + $this->input = $input; + $this->output = $output; + } +} diff --git a/src/crafting/json/ShapelessRecipeData.php b/src/crafting/json/ShapelessRecipeData.php new file mode 100644 index 000000000..453b349dd --- /dev/null +++ b/src/crafting/json/ShapelessRecipeData.php @@ -0,0 +1,61 @@ + + */ + public array $input; + + /** + * @required + * @var ItemStackData[] + * @phpstan-var list + */ + public array $output; + + /** @required */ + public string $block; + + /** @required */ + public int $priority; + + /** + * @param RecipeIngredientData[] $input + * @param ItemStackData[] $output + * + * @phpstan-param list $input + * @phpstan-param list $output + */ + public function __construct(array $input, array $output, string $block, int $priority){ + $this->block = $block; + $this->priority = $priority; + $this->input = $input; + $this->output = $output; + } +} diff --git a/src/data/bedrock/item/BlockItemIdMap.php b/src/data/bedrock/item/BlockItemIdMap.php index 5d1376aef..89ae14f78 100644 --- a/src/data/bedrock/item/BlockItemIdMap.php +++ b/src/data/bedrock/item/BlockItemIdMap.php @@ -27,7 +27,7 @@ use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\SingletonTrait; use pocketmine\utils\Utils; use Webmozart\PathUtil\Path; -use function array_search; +use function array_flip; use function file_get_contents; use function is_array; use function json_decode; @@ -52,20 +52,25 @@ final class BlockItemIdMap{ return new self($map); } + /** + * @var string[] + * @phpstan-var array + */ + private array $itemToBlockId; + /** * @param string[] $blockToItemId * @phpstan-param array $blockToItemId */ - public function __construct(private array $blockToItemId){} + public function __construct(private array $blockToItemId){ + $this->itemToBlockId = array_flip($this->blockToItemId); + } public function lookupItemId(string $blockId) : ?string{ return $this->blockToItemId[$blockId] ?? null; } public function lookupBlockId(string $itemId) : ?string{ - //we don't need this for any functionality, so we're not concerned about performance here - //however, it might be nice to have for debugging - $blockId = array_search($itemId, $this->blockToItemId, true); - return $blockId !== false ? $blockId : null; + return $this->itemToBlockId[$itemId] ?? null; } } diff --git a/src/inventory/CreativeInventory.php b/src/inventory/CreativeInventory.php index 9c405b92c..5dd293b4c 100644 --- a/src/inventory/CreativeInventory.php +++ b/src/inventory/CreativeInventory.php @@ -23,13 +23,11 @@ declare(strict_types=1); namespace pocketmine\inventory; -use pocketmine\data\SavedDataLoadingException; +use pocketmine\crafting\CraftingManagerFromDataHelper; +use pocketmine\crafting\json\ItemStackData; use pocketmine\item\Item; use pocketmine\utils\SingletonTrait; use Webmozart\PathUtil\Path; -use function file_get_contents; -use function is_array; -use function json_decode; final class CreativeInventory{ use SingletonTrait; @@ -38,18 +36,13 @@ final class CreativeInventory{ private array $creative = []; private function __construct(){ - $creativeItems = json_decode(file_get_contents(Path::join(\pocketmine\BEDROCK_DATA_PATH, "creativeitems.json")), true); - if(!is_array($creativeItems)){ - throw new SavedDataLoadingException("Invalid creative items file, expected array as root type"); - } - + $creativeItems = CraftingManagerFromDataHelper::loadJsonArrayOfObjectsFile( + Path::join(\pocketmine\BEDROCK_DATA_PATH, "creativeitems.json"), + ItemStackData::class + ); foreach($creativeItems as $data){ - if(!is_array($data)){ - throw new SavedDataLoadingException("Invalid creative items file, expected array as item type"); - } - try{ - $item = Item::legacyJsonDeserialize($data); - }catch(SavedDataLoadingException){ + $item = CraftingManagerFromDataHelper::deserializeItemStack($data); + if($item === null){ //unknown item continue; } diff --git a/src/network/mcpe/cache/CraftingDataCache.php b/src/network/mcpe/cache/CraftingDataCache.php index 6d86b8555..84ff5e739 100644 --- a/src/network/mcpe/cache/CraftingDataCache.php +++ b/src/network/mcpe/cache/CraftingDataCache.php @@ -149,8 +149,8 @@ final class CraftingDataCache{ $potionTypeRecipes = []; foreach($manager->getPotionTypeRecipes() as $recipe){ - $input = $converter->coreItemStackToNet($recipe->getInput()); - $ingredient = $converter->coreItemStackToNet($recipe->getIngredient()); + $input = $converter->coreRecipeIngredientToNet($recipe->getInput()); + $ingredient = $converter->coreRecipeIngredientToNet($recipe->getIngredient()); $output = $converter->coreItemStackToNet($recipe->getOutput()); $potionTypeRecipes[] = new ProtocolPotionTypeRecipe( $input->getId(), @@ -166,7 +166,7 @@ final class CraftingDataCache{ $itemTypeDictionary = GlobalItemTypeDictionary::getInstance()->getDictionary(); foreach($manager->getPotionContainerChangeRecipes() as $recipe){ $input = $itemTypeDictionary->fromStringId($recipe->getInputItemId()); - $ingredient = $converter->coreItemStackToNet($recipe->getIngredient()); + $ingredient = $converter->coreRecipeIngredientToNet($recipe->getIngredient()); $output = $itemTypeDictionary->fromStringId($recipe->getOutputItemId()); $potionContainerChangeRecipes[] = new ProtocolPotionContainerChangeRecipe( $input, diff --git a/tests/phpstan/configs/actual-problems.neon b/tests/phpstan/configs/actual-problems.neon index 2ec7031d0..8a98fd4a0 100644 --- a/tests/phpstan/configs/actual-problems.neon +++ b/tests/phpstan/configs/actual-problems.neon @@ -575,21 +575,6 @@ parameters: count: 1 path: ../../../src/entity/projectile/Projectile.php - - - message: "#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\\.$#" - count: 1 - path: ../../../src/inventory/CreativeInventory.php - - - - message: "#^Parameter \\#1 \\$data of static method pocketmine\\\\item\\\\Item\\:\\:jsonDeserialize\\(\\) expects array, mixed given\\.$#" - count: 1 - path: ../../../src/inventory/CreativeInventory.php - - - - message: "#^Parameter \\#1 \\$json of function json_decode expects string, string\\|false given\\.$#" - count: 1 - path: ../../../src/inventory/CreativeInventory.php - - message: "#^Parameter \\#2 \\$recipe of class pocketmine\\\\event\\\\inventory\\\\CraftItemEvent constructor expects pocketmine\\\\crafting\\\\CraftingRecipe, pocketmine\\\\crafting\\\\CraftingRecipe\\|null given\\.$#" count: 1