mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-04-20 16:00:20 +00:00
Burn meta wildcards from Item, allow more dynamic recipe inputs
this was an obstacle for getting rid of legacy item IDs.
This commit is contained in:
parent
bc5a600d59
commit
55cb68e5b5
@ -184,7 +184,7 @@ class CraftingManager{
|
||||
public function registerPotionTypeRecipe(PotionTypeRecipe $recipe) : void{
|
||||
$input = $recipe->getInput();
|
||||
$ingredient = $recipe->getIngredient();
|
||||
$this->potionTypeRecipes[$input->getId() . ":" . $input->getMeta()][$ingredient->getId() . ":" . ($ingredient->hasAnyDamageValue() ? "?" : $ingredient->getMeta())] = $recipe;
|
||||
$this->potionTypeRecipes[$input->getId() . ":" . $input->getMeta()][$ingredient->getId() . ":" . $ingredient->getMeta()] = $recipe;
|
||||
|
||||
foreach($this->recipeRegisteredCallbacks as $callback){
|
||||
$callback();
|
||||
@ -193,7 +193,7 @@ class CraftingManager{
|
||||
|
||||
public function registerPotionContainerChangeRecipe(PotionContainerChangeRecipe $recipe) : void{
|
||||
$ingredient = $recipe->getIngredient();
|
||||
$this->potionContainerChangeRecipes[$recipe->getInputItemId()][$ingredient->getId() . ":" . ($ingredient->hasAnyDamageValue() ? "?" : $ingredient->getMeta())] = $recipe;
|
||||
$this->potionContainerChangeRecipes[$recipe->getInputItemId()][$ingredient->getId() . ":" . $ingredient->getMeta()] = $recipe;
|
||||
|
||||
foreach($this->recipeRegisteredCallbacks as $callback){
|
||||
$callback();
|
||||
@ -253,8 +253,6 @@ class CraftingManager{
|
||||
|
||||
public function matchBrewingRecipe(Item $input, Item $ingredient) : ?BrewingRecipe{
|
||||
return $this->potionTypeRecipes[$input->getId() . ":" . $input->getMeta()][$ingredient->getId() . ":" . $ingredient->getMeta()] ??
|
||||
$this->potionTypeRecipes[$input->getId() . ":" . $input->getMeta()][$ingredient->getId() . ":?"] ??
|
||||
$this->potionContainerChangeRecipes[$input->getId()][$ingredient->getId() . ":" . $ingredient->getMeta()] ??
|
||||
$this->potionContainerChangeRecipes[$input->getId()][$ingredient->getId() . ":?"] ?? null;
|
||||
$this->potionContainerChangeRecipes[$input->getId()][$ingredient->getId() . ":" . $ingredient->getMeta()] ?? null;
|
||||
}
|
||||
}
|
||||
|
@ -23,14 +23,17 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\crafting;
|
||||
|
||||
use pocketmine\data\bedrock\item\ItemTypeDeserializeException;
|
||||
use pocketmine\item\Durable;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\item\ItemFactory;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\Utils;
|
||||
use pocketmine\world\format\io\GlobalItemDataHandlers;
|
||||
use function array_map;
|
||||
use function file_get_contents;
|
||||
use function is_array;
|
||||
use function is_int;
|
||||
use function json_decode;
|
||||
|
||||
final class CraftingManagerFromDataHelper{
|
||||
@ -41,7 +44,7 @@ final class CraftingManagerFromDataHelper{
|
||||
private static function containsUnknownItems(array $items) : bool{
|
||||
$factory = ItemFactory::getInstance();
|
||||
foreach($items as $item){
|
||||
if($item instanceof Durable || $item->hasAnyDamageValue()){
|
||||
if($item instanceof Durable){
|
||||
//TODO: this check is imperfect and might cause problems if meta 0 isn't used for some reason
|
||||
if(!$factory->isRegistered($item->getId())){
|
||||
return true;
|
||||
@ -54,6 +57,30 @@ final class CraftingManagerFromDataHelper{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
|
||||
return new MetaWildcardRecipeIngredient($typeData->getTypeData()->getName());
|
||||
}
|
||||
|
||||
//TODO: we need to stop using jsonDeserialize for this
|
||||
$item = Item::jsonDeserialize($data);
|
||||
|
||||
return self::containsUnknownItems([$item]) ? null : 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)){
|
||||
@ -61,6 +88,7 @@ final class CraftingManagerFromDataHelper{
|
||||
}
|
||||
$result = new CraftingManager();
|
||||
|
||||
$ingredientDeserializerFunc = \Closure::fromCallable([self::class, "deserializeIngredient"]);
|
||||
$itemDeserializerFunc = \Closure::fromCallable([Item::class, 'jsonDeserialize']);
|
||||
|
||||
foreach($recipes["shapeless"] as $recipe){
|
||||
@ -73,9 +101,16 @@ final class CraftingManagerFromDataHelper{
|
||||
if($recipeType === null){
|
||||
continue;
|
||||
}
|
||||
$inputs = array_map($itemDeserializerFunc, $recipe["input"]);
|
||||
$inputs = [];
|
||||
foreach($recipe["input"] as $inputData){
|
||||
$input = $ingredientDeserializerFunc($inputData);
|
||||
if($input === null){ //unknown input item
|
||||
continue;
|
||||
}
|
||||
$inputs[] = $input;
|
||||
}
|
||||
$outputs = array_map($itemDeserializerFunc, $recipe["output"]);
|
||||
if(self::containsUnknownItems($inputs) || self::containsUnknownItems($outputs)){
|
||||
if(self::containsUnknownItems($outputs)){
|
||||
continue;
|
||||
}
|
||||
$result->registerShapelessRecipe(new ShapelessRecipe(
|
||||
@ -88,9 +123,16 @@ final class CraftingManagerFromDataHelper{
|
||||
if($recipe["block"] !== "crafting_table"){ //TODO: filter others out for now to avoid breaking economics
|
||||
continue;
|
||||
}
|
||||
$inputs = array_map($itemDeserializerFunc, $recipe["input"]);
|
||||
$inputs = [];
|
||||
foreach($recipe["input"] as $symbol => $inputData){
|
||||
$input = $ingredientDeserializerFunc($inputData);
|
||||
if($input === null){ //unknown input item
|
||||
continue;
|
||||
}
|
||||
$inputs[$symbol] = $input;
|
||||
}
|
||||
$outputs = array_map($itemDeserializerFunc, $recipe["output"]);
|
||||
if(self::containsUnknownItems($inputs) || self::containsUnknownItems($outputs)){
|
||||
if(self::containsUnknownItems($outputs)){
|
||||
continue;
|
||||
}
|
||||
$result->registerShapedRecipe(new ShapedRecipe(
|
||||
@ -111,8 +153,8 @@ final class CraftingManagerFromDataHelper{
|
||||
continue;
|
||||
}
|
||||
$output = Item::jsonDeserialize($recipe["output"]);
|
||||
$input = Item::jsonDeserialize($recipe["input"]);
|
||||
if(self::containsUnknownItems([$output, $input])){
|
||||
$input = self::deserializeIngredient($recipe["input"]);
|
||||
if($input === null || self::containsUnknownItems([$output])){
|
||||
continue;
|
||||
}
|
||||
$result->getFurnaceRecipeManager($furnaceType)->register(new FurnaceRecipe(
|
||||
@ -135,9 +177,9 @@ final class CraftingManagerFromDataHelper{
|
||||
));
|
||||
}
|
||||
foreach($recipes["potion_container_change"] as $recipe){
|
||||
$input = ItemFactory::getInstance()->get($recipe["input_item_id"], -1);
|
||||
$input = ItemFactory::getInstance()->get($recipe["input_item_id"]);
|
||||
$ingredient = Item::jsonDeserialize($recipe["ingredient"]);
|
||||
$output = ItemFactory::getInstance()->get($recipe["output_item_id"], -1);
|
||||
$output = ItemFactory::getInstance()->get($recipe["output_item_id"]);
|
||||
|
||||
if(self::containsUnknownItems([$input, $ingredient, $output])){
|
||||
continue;
|
||||
|
@ -29,7 +29,7 @@ interface CraftingRecipe{
|
||||
/**
|
||||
* Returns a list of items needed to craft this recipe. This MUST NOT include Air items or items with a zero count.
|
||||
*
|
||||
* @return Item[]
|
||||
* @return RecipeIngredient[]
|
||||
*/
|
||||
public function getIngredientList() : array;
|
||||
|
||||
|
56
src/crafting/ExactRecipeIngredient.php
Normal file
56
src/crafting/ExactRecipeIngredient.php
Normal file
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\crafting;
|
||||
|
||||
use pocketmine\item\Item;
|
||||
|
||||
/**
|
||||
* Recipe ingredient that matches exactly one item, without wildcards.
|
||||
* Note that recipe inputs cannot require NBT.
|
||||
*/
|
||||
final class ExactRecipeIngredient implements RecipeIngredient{
|
||||
|
||||
public function __construct(private Item $item){
|
||||
if($item->isNull()){
|
||||
throw new \InvalidArgumentException("Recipe ingredients must not be air items");
|
||||
}
|
||||
if($item->getCount() !== 1){
|
||||
throw new \InvalidArgumentException("Recipe ingredients cannot require count");
|
||||
}
|
||||
$this->item = clone $item;
|
||||
}
|
||||
|
||||
public function getItem() : Item{ return clone $this->item; }
|
||||
|
||||
public function accepts(Item $item) : bool{
|
||||
//client-side, recipe inputs can't actually require NBT
|
||||
//but on the PM side, we currently check for it if the input requires it, so we have to continue to do so for
|
||||
//the sake of consistency
|
||||
return $item->getCount() >= 1 && $this->item->equals($item, true, $this->item->hasNamedTag());
|
||||
}
|
||||
|
||||
public function __toString() : string{
|
||||
return "ExactRecipeIngredient(" . $this->item . ")";
|
||||
}
|
||||
}
|
@ -29,14 +29,13 @@ class FurnaceRecipe{
|
||||
|
||||
public function __construct(
|
||||
private Item $result,
|
||||
private Item $ingredient
|
||||
private RecipeIngredient $ingredient
|
||||
){
|
||||
$this->result = clone $result;
|
||||
$this->ingredient = clone $ingredient;
|
||||
}
|
||||
|
||||
public function getInput() : Item{
|
||||
return clone $this->ingredient;
|
||||
public function getInput() : RecipeIngredient{
|
||||
return $this->ingredient;
|
||||
}
|
||||
|
||||
public function getResult() : Item{
|
||||
|
@ -25,11 +25,18 @@ namespace pocketmine\crafting;
|
||||
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\utils\ObjectSet;
|
||||
use function morton2d_encode;
|
||||
|
||||
final class FurnaceRecipeManager{
|
||||
/** @var FurnaceRecipe[] */
|
||||
protected array $furnaceRecipes = [];
|
||||
|
||||
/**
|
||||
* @var FurnaceRecipe[]
|
||||
* @phpstan-var array<int, FurnaceRecipe>
|
||||
*/
|
||||
private array $lookupCache = [];
|
||||
|
||||
/** @phpstan-var ObjectSet<\Closure(FurnaceRecipe) : void> */
|
||||
private ObjectSet $recipeRegisteredCallbacks;
|
||||
|
||||
@ -52,14 +59,27 @@ final class FurnaceRecipeManager{
|
||||
}
|
||||
|
||||
public function register(FurnaceRecipe $recipe) : void{
|
||||
$input = $recipe->getInput();
|
||||
$this->furnaceRecipes[$input->getId() . ":" . ($input->hasAnyDamageValue() ? "?" : $input->getMeta())] = $recipe;
|
||||
$this->furnaceRecipes[] = $recipe;
|
||||
foreach($this->recipeRegisteredCallbacks as $callback){
|
||||
$callback($recipe);
|
||||
}
|
||||
}
|
||||
|
||||
public function match(Item $input) : ?FurnaceRecipe{
|
||||
return $this->furnaceRecipes[$input->getId() . ":" . $input->getMeta()] ?? $this->furnaceRecipes[$input->getId() . ":?"] ?? null;
|
||||
$index = morton2d_encode($input->getId(), $input->getMeta());
|
||||
$simpleRecipe = $this->lookupCache[$index] ?? null;
|
||||
if($simpleRecipe !== null){
|
||||
return $simpleRecipe;
|
||||
}
|
||||
|
||||
foreach($this->furnaceRecipes as $recipe){
|
||||
if($recipe->getInput()->accepts($input)){
|
||||
//remember that this item is accepted by this recipe, so we don't need to bruteforce it again
|
||||
$this->lookupCache[$index] = $recipe;
|
||||
return $recipe;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
57
src/crafting/MetaWildcardRecipeIngredient.php
Normal file
57
src/crafting/MetaWildcardRecipeIngredient.php
Normal file
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\crafting;
|
||||
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\world\format\io\GlobalItemDataHandlers;
|
||||
|
||||
/**
|
||||
* Recipe ingredient that matches items by their Minecraft ID only. This is used for things like the crafting table
|
||||
* recipe from planks (multiple types of planks are accepted).
|
||||
*
|
||||
* WARNING: Plugins shouldn't usually use this. This is a hack that relies on internal Minecraft behaviour, which might
|
||||
* change or break at any time.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class MetaWildcardRecipeIngredient implements RecipeIngredient{
|
||||
|
||||
public function __construct(
|
||||
private string $itemId,
|
||||
){}
|
||||
|
||||
public function getItemId() : string{ return $this->itemId; }
|
||||
|
||||
public function accepts(Item $item) : bool{
|
||||
if($item->getCount() < 1){
|
||||
return false;
|
||||
}
|
||||
|
||||
return GlobalItemDataHandlers::getSerializer()->serializeType($item)->getName() === $this->itemId;
|
||||
}
|
||||
|
||||
public function __toString() : string{
|
||||
return "MetaWildcardRecipeIngredient($this->itemId)";
|
||||
}
|
||||
}
|
31
src/crafting/RecipeIngredient.php
Normal file
31
src/crafting/RecipeIngredient.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\crafting;
|
||||
|
||||
use pocketmine\item\Item;
|
||||
|
||||
interface RecipeIngredient extends \Stringable{
|
||||
|
||||
public function accepts(Item $item) : bool;
|
||||
}
|
@ -24,7 +24,6 @@ declare(strict_types=1);
|
||||
namespace pocketmine\crafting;
|
||||
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\item\VanillaItems;
|
||||
use pocketmine\utils\Utils;
|
||||
use function array_values;
|
||||
use function count;
|
||||
@ -35,7 +34,7 @@ use function strpos;
|
||||
class ShapedRecipe implements CraftingRecipe{
|
||||
/** @var string[] */
|
||||
private array $shape = [];
|
||||
/** @var Item[] char => Item map */
|
||||
/** @var RecipeIngredient[] char => RecipeIngredient map */
|
||||
private array $ingredientList = [];
|
||||
/** @var Item[] */
|
||||
private array $results = [];
|
||||
@ -46,15 +45,15 @@ class ShapedRecipe implements CraftingRecipe{
|
||||
/**
|
||||
* Constructs a ShapedRecipe instance.
|
||||
*
|
||||
* @param string[] $shape <br>
|
||||
* @param string[] $shape <br>
|
||||
* Array of 1, 2, or 3 strings representing the rows of the recipe.
|
||||
* This accepts an array of 1, 2 or 3 strings. Each string should be of the same length and must be at most 3
|
||||
* characters long. Each character represents a unique type of ingredient. Spaces are interpreted as air.
|
||||
* @param Item[] $ingredients <br>
|
||||
* @param RecipeIngredient[] $ingredients <br>
|
||||
* Char => Item map of items to be set into the shape.
|
||||
* This accepts an array of Items, indexed by character. Every unique character (except space) in the shape
|
||||
* array MUST have a corresponding item in this list. Space character is automatically treated as air.
|
||||
* @param Item[] $results List of items that this recipe produces when crafted.
|
||||
* @param Item[] $results List of items that this recipe produces when crafted.
|
||||
*
|
||||
* Note: Recipes **do not** need to be square. Do NOT add padding for empty rows/columns.
|
||||
*/
|
||||
@ -119,7 +118,7 @@ class ShapedRecipe implements CraftingRecipe{
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Item[][]
|
||||
* @return (RecipeIngredient|null)[][]
|
||||
*/
|
||||
public function getIngredientMap() : array{
|
||||
$ingredients = [];
|
||||
@ -134,7 +133,7 @@ class ShapedRecipe implements CraftingRecipe{
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Item[]
|
||||
* @return RecipeIngredient[]
|
||||
*/
|
||||
public function getIngredientList() : array{
|
||||
$ingredients = [];
|
||||
@ -142,7 +141,7 @@ class ShapedRecipe implements CraftingRecipe{
|
||||
for($y = 0; $y < $this->height; ++$y){
|
||||
for($x = 0; $x < $this->width; ++$x){
|
||||
$ingredient = $this->getIngredient($x, $y);
|
||||
if(!$ingredient->isNull()){
|
||||
if($ingredient !== null){
|
||||
$ingredients[] = $ingredient;
|
||||
}
|
||||
}
|
||||
@ -151,9 +150,8 @@ class ShapedRecipe implements CraftingRecipe{
|
||||
return $ingredients;
|
||||
}
|
||||
|
||||
public function getIngredient(int $x, int $y) : Item{
|
||||
$exists = $this->ingredientList[$this->shape[$y][$x]] ?? null;
|
||||
return $exists !== null ? clone $exists : VanillaItems::AIR();
|
||||
public function getIngredient(int $x, int $y) : ?RecipeIngredient{
|
||||
return $this->ingredientList[$this->shape[$y][$x]] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -170,7 +168,12 @@ class ShapedRecipe implements CraftingRecipe{
|
||||
|
||||
$given = $grid->getIngredient($reverse ? $this->width - $x - 1 : $x, $y);
|
||||
$required = $this->getIngredient($x, $y);
|
||||
if(!$required->equals($given, !$required->hasAnyDamageValue(), $required->hasNamedTag()) || $required->getCount() > $given->getCount()){
|
||||
|
||||
if($required === null){
|
||||
if(!$given->isNull()){
|
||||
return false; //hole, such as that in the center of a chest recipe, should not be filled
|
||||
}
|
||||
}elseif(!$required->accepts($given)){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -28,30 +28,24 @@ use pocketmine\utils\Utils;
|
||||
use function count;
|
||||
|
||||
class ShapelessRecipe implements CraftingRecipe{
|
||||
/** @var Item[] */
|
||||
/** @var RecipeIngredient[] */
|
||||
private array $ingredients = [];
|
||||
/** @var Item[] */
|
||||
private array $results;
|
||||
private ShapelessRecipeType $type;
|
||||
|
||||
/**
|
||||
* @param Item[] $ingredients No more than 9 total. This applies to sum of item stack counts, not count of array.
|
||||
* @param RecipeIngredient[] $ingredients No more than 9 total. This applies to sum of item stack counts, not count of array.
|
||||
* @param Item[] $results List of result items created by this recipe.
|
||||
* TODO: we'll want to make the type parameter mandatory in PM5
|
||||
*/
|
||||
public function __construct(array $ingredients, array $results, ?ShapelessRecipeType $type = null){
|
||||
$this->type = $type ?? ShapelessRecipeType::CRAFTING();
|
||||
foreach($ingredients as $item){
|
||||
//Ensure they get split up properly
|
||||
if(count($this->ingredients) + $item->getCount() > 9){
|
||||
throw new \InvalidArgumentException("Shapeless recipes cannot have more than 9 ingredients");
|
||||
}
|
||||
|
||||
while($item->getCount() > 0){
|
||||
$this->ingredients[] = $item->pop();
|
||||
}
|
||||
if(count($ingredients) > 9){
|
||||
throw new \InvalidArgumentException("Shapeless recipes cannot have more than 9 ingredients");
|
||||
}
|
||||
|
||||
$this->ingredients = $ingredients;
|
||||
$this->results = Utils::cloneObjectArray($results);
|
||||
}
|
||||
|
||||
@ -71,28 +65,23 @@ class ShapelessRecipe implements CraftingRecipe{
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Item[]
|
||||
* @return RecipeIngredient[]
|
||||
*/
|
||||
public function getIngredientList() : array{
|
||||
return Utils::cloneObjectArray($this->ingredients);
|
||||
return $this->ingredients;
|
||||
}
|
||||
|
||||
public function getIngredientCount() : int{
|
||||
$count = 0;
|
||||
foreach($this->ingredients as $ingredient){
|
||||
$count += $ingredient->getCount();
|
||||
}
|
||||
|
||||
return $count;
|
||||
return count($this->ingredients);
|
||||
}
|
||||
|
||||
public function matchesCraftingGrid(CraftingGrid $grid) : bool{
|
||||
//don't pack the ingredients - shapeless recipes require that each ingredient be in a separate slot
|
||||
$input = $grid->getContents();
|
||||
|
||||
foreach($this->ingredients as $needItem){
|
||||
foreach($this->ingredients as $ingredient){
|
||||
foreach($input as $j => $haveItem){
|
||||
if($haveItem->equals($needItem, !$needItem->hasAnyDamageValue(), $needItem->hasNamedTag()) && $haveItem->getCount() >= $needItem->getCount()){
|
||||
if($ingredient->accepts($haveItem)){
|
||||
unset($input[$j]);
|
||||
continue 2;
|
||||
}
|
||||
|
@ -72,9 +72,6 @@ final class ItemSerializer{
|
||||
* @phpstan-param \Closure(TItemType) : Data $serializer
|
||||
*/
|
||||
public function map(Item $item, \Closure $serializer) : void{
|
||||
if($item->hasAnyDamageValue()){
|
||||
throw new \InvalidArgumentException("Cannot serialize a recipe wildcard");
|
||||
}
|
||||
$index = $item->getTypeId();
|
||||
if(isset($this->itemSerializers[$index])){
|
||||
//TODO: REMOVE ME
|
||||
@ -106,9 +103,6 @@ final class ItemSerializer{
|
||||
if($item->isNull()){
|
||||
throw new \InvalidArgumentException("Cannot serialize a null itemstack");
|
||||
}
|
||||
if($item->hasAnyDamageValue()){
|
||||
throw new \InvalidArgumentException("Cannot serialize a recipe input as a saved itemstack");
|
||||
}
|
||||
if($item instanceof ItemBlock){
|
||||
$data = $this->serializeBlockItem($item->getBlock());
|
||||
}else{
|
||||
|
@ -70,6 +70,47 @@ final class ItemDataUpgrader{
|
||||
ksort($this->idMetaUpgradeSchemas, SORT_NUMERIC);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function replaces the legacy ItemFactory::get().
|
||||
*
|
||||
* Unlike ItemFactory::get(), it returns a SavedItemStackData which you can do with as you please.
|
||||
* If you want to deserialize it into a PocketMine-MP itemstack, pass it to the ItemDeserializer.
|
||||
*
|
||||
* @see ItemDataUpgrader::upgradeItemTypeDataInt()
|
||||
*/
|
||||
public function upgradeItemTypeDataString(string $rawNameId, int $meta, int $count, ?CompoundTag $nbt) : SavedItemStackData{
|
||||
if(($r12BlockId = $this->r12ItemIdToBlockIdMap->itemIdToBlockId($rawNameId)) !== null){
|
||||
$blockStateData = $this->blockDataUpgrader->upgradeStringIdMeta($r12BlockId, $meta);
|
||||
}else{
|
||||
//probably a standard item
|
||||
$blockStateData = null;
|
||||
}
|
||||
|
||||
[$newNameId, $newMeta] = $this->upgradeItemStringIdMeta($rawNameId, $meta);
|
||||
|
||||
//TODO: this won't account for spawn eggs from before 1.16.100 - perhaps we're lucky and they just left the meta in there anyway?
|
||||
|
||||
return new SavedItemStackData(
|
||||
new SavedItemData($newNameId, $newMeta, $blockStateData, $nbt),
|
||||
$count,
|
||||
null,
|
||||
null,
|
||||
[],
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function replaces the legacy ItemFactory::get().
|
||||
*/
|
||||
public function upgradeItemTypeDataInt(int $legacyNumericId, int $meta, int $count, ?CompoundTag $nbt) : SavedItemStackData{
|
||||
$rawNameId = $this->legacyIntToStringIdMap->legacyToString($legacyNumericId);
|
||||
if($rawNameId === null){
|
||||
throw new SavedDataLoadingException("Unmapped legacy item ID $legacyNumericId");
|
||||
}
|
||||
return $this->upgradeItemTypeDataString($rawNameId, $meta, $count, $nbt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SavedDataLoadingException
|
||||
*/
|
||||
|
@ -105,10 +105,9 @@ abstract class BaseInventory implements Inventory{
|
||||
|
||||
public function contains(Item $item) : bool{
|
||||
$count = max(1, $item->getCount());
|
||||
$checkDamage = !$item->hasAnyDamageValue();
|
||||
$checkTags = $item->hasNamedTag();
|
||||
foreach($this->getContents() as $i){
|
||||
if($item->equals($i, $checkDamage, $checkTags)){
|
||||
if($item->equals($i, true, $checkTags)){
|
||||
$count -= $i->getCount();
|
||||
if($count <= 0){
|
||||
return true;
|
||||
@ -121,23 +120,22 @@ abstract class BaseInventory implements Inventory{
|
||||
|
||||
public function all(Item $item) : array{
|
||||
$slots = [];
|
||||
$checkDamage = !$item->hasAnyDamageValue();
|
||||
$checkTags = $item->hasNamedTag();
|
||||
foreach($this->getContents() as $index => $i){
|
||||
if($item->equals($i, $checkDamage, $checkTags)){
|
||||
if($item->equals($i, true, $checkTags)){
|
||||
$slots[$index] = $i;
|
||||
}
|
||||
}
|
||||
|
||||
return $slots;
|
||||
}
|
||||
|
||||
public function first(Item $item, bool $exact = false) : int{
|
||||
$count = $exact ? $item->getCount() : max(1, $item->getCount());
|
||||
$checkDamage = $exact || !$item->hasAnyDamageValue();
|
||||
$checkTags = $exact || $item->hasNamedTag();
|
||||
|
||||
foreach($this->getContents() as $index => $i){
|
||||
if($item->equals($i, $checkDamage, $checkTags) && ($i->getCount() === $count || (!$exact && $i->getCount() > $count))){
|
||||
if($item->equals($i, true, $checkTags) && ($i->getCount() === $count || (!$exact && $i->getCount() > $count))){
|
||||
return $index;
|
||||
}
|
||||
}
|
||||
@ -245,11 +243,10 @@ abstract class BaseInventory implements Inventory{
|
||||
}
|
||||
|
||||
public function remove(Item $item) : void{
|
||||
$checkDamage = !$item->hasAnyDamageValue();
|
||||
$checkTags = $item->hasNamedTag();
|
||||
|
||||
foreach($this->getContents() as $index => $i){
|
||||
if($item->equals($i, $checkDamage, $checkTags)){
|
||||
if($item->equals($i, true, $checkTags)){
|
||||
$this->clear($index);
|
||||
}
|
||||
}
|
||||
@ -272,7 +269,7 @@ abstract class BaseInventory implements Inventory{
|
||||
}
|
||||
|
||||
foreach($itemSlots as $index => $slot){
|
||||
if($slot->equals($item, !$slot->hasAnyDamageValue(), $slot->hasNamedTag())){
|
||||
if($slot->equals($item, true, $slot->hasNamedTag())){
|
||||
$amount = min($item->getCount(), $slot->getCount());
|
||||
$slot->setCount($slot->getCount() - $amount);
|
||||
$item->setCount($item->getCount() - $amount);
|
||||
|
@ -25,12 +25,18 @@ namespace pocketmine\inventory\transaction;
|
||||
|
||||
use pocketmine\crafting\CraftingManager;
|
||||
use pocketmine\crafting\CraftingRecipe;
|
||||
use pocketmine\crafting\RecipeIngredient;
|
||||
use pocketmine\event\inventory\CraftItemEvent;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\utils\Utils;
|
||||
use function array_fill_keys;
|
||||
use function array_keys;
|
||||
use function array_pop;
|
||||
use function count;
|
||||
use function intdiv;
|
||||
use function min;
|
||||
use function uasort;
|
||||
|
||||
/**
|
||||
* This transaction type is specialized for crafting validation. It shares most of the same semantics of the base
|
||||
@ -63,13 +69,110 @@ class CraftingTransaction extends InventoryTransaction{
|
||||
$this->craftingManager = $craftingManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Item[] $providedItems
|
||||
* @return Item[]
|
||||
*/
|
||||
private static function packItems(array $providedItems) : array{
|
||||
$packedProvidedItems = [];
|
||||
while(count($providedItems) > 0){
|
||||
$item = array_pop($providedItems);
|
||||
foreach($providedItems as $k => $otherItem){
|
||||
if($item->canStackWith($otherItem)){
|
||||
$item->setCount($item->getCount() + $otherItem->getCount());
|
||||
unset($providedItems[$k]);
|
||||
}
|
||||
}
|
||||
$packedProvidedItems[] = $item;
|
||||
}
|
||||
|
||||
return $packedProvidedItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Item[] $providedItems
|
||||
* @param RecipeIngredient[] $recipeIngredients
|
||||
*/
|
||||
public static function matchIngredients(array $providedItems, array $recipeIngredients, int $expectedIterations) : void{
|
||||
if(count($recipeIngredients) === 0){
|
||||
throw new TransactionValidationException("No recipe ingredients given");
|
||||
}
|
||||
if(count($providedItems) === 0){
|
||||
throw new TransactionValidationException("No transaction items given");
|
||||
}
|
||||
|
||||
$packedProvidedItems = self::packItems(Utils::cloneObjectArray($providedItems));
|
||||
$packedProvidedItemMatches = array_fill_keys(array_keys($packedProvidedItems), 0);
|
||||
|
||||
$recipeIngredientMatches = [];
|
||||
|
||||
foreach($recipeIngredients as $ingredientIndex => $recipeIngredient){
|
||||
$acceptedItems = [];
|
||||
foreach($packedProvidedItems as $itemIndex => $packedItem){
|
||||
if($recipeIngredient->accepts($packedItem)){
|
||||
$packedProvidedItemMatches[$itemIndex]++;
|
||||
$acceptedItems[$itemIndex] = $itemIndex;
|
||||
}
|
||||
}
|
||||
|
||||
if(count($acceptedItems) === 0){
|
||||
throw new TransactionValidationException("No provided items satisfy ingredient requirement $recipeIngredient");
|
||||
}
|
||||
|
||||
$recipeIngredientMatches[$ingredientIndex] = $acceptedItems;
|
||||
}
|
||||
|
||||
foreach($packedProvidedItemMatches as $itemIndex => $itemMatchCount){
|
||||
if($itemMatchCount === 0){
|
||||
$item = $packedProvidedItems[$itemIndex];
|
||||
throw new TransactionValidationException("Provided item $item is not accepted by any recipe ingredient");
|
||||
}
|
||||
}
|
||||
|
||||
//Most picky ingredients first - avoid picky ingredient getting their items stolen by wildcard ingredients
|
||||
//TODO: this is still insufficient when multiple wildcard ingredients have overlaps, but we don't (yet) have to
|
||||
//worry about those.
|
||||
uasort($recipeIngredientMatches, fn(array $a, array $b) => count($a) <=> count($b));
|
||||
|
||||
foreach($recipeIngredientMatches as $ingredientIndex => $acceptedItems){
|
||||
$needed = $expectedIterations;
|
||||
|
||||
foreach($packedProvidedItems as $itemIndex => $item){
|
||||
if(!isset($acceptedItems[$itemIndex])){
|
||||
continue;
|
||||
}
|
||||
|
||||
$taken = min($needed, $item->getCount());
|
||||
$needed -= $taken;
|
||||
$item->setCount($item->getCount() - $taken);
|
||||
|
||||
if($item->getCount() === 0){
|
||||
unset($packedProvidedItems[$itemIndex]);
|
||||
}
|
||||
|
||||
if($needed === 0){
|
||||
//validation passed!
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
$recipeIngredient = $recipeIngredients[$ingredientIndex];
|
||||
$actualIterations = $expectedIterations - $needed;
|
||||
throw new TransactionValidationException("Not enough items to satisfy recipe ingredient $recipeIngredient for $expectedIterations (only have enough items for $actualIterations iterations)");
|
||||
}
|
||||
|
||||
if(count($packedProvidedItems) > 0){
|
||||
throw new TransactionValidationException("Not all provided items were used");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Item[] $txItems
|
||||
* @param Item[] $recipeItems
|
||||
*
|
||||
* @throws TransactionValidationException
|
||||
*/
|
||||
protected function matchRecipeItems(array $txItems, array $recipeItems, bool $wildcards, int $iterations = 0) : int{
|
||||
protected function matchOutputs(array $txItems, array $recipeItems) : int{
|
||||
if(count($recipeItems) === 0){
|
||||
throw new TransactionValidationException("No recipe items given");
|
||||
}
|
||||
@ -77,6 +180,7 @@ class CraftingTransaction extends InventoryTransaction{
|
||||
throw new TransactionValidationException("No transaction items given");
|
||||
}
|
||||
|
||||
$iterations = 0;
|
||||
while(count($recipeItems) > 0){
|
||||
/** @var Item $recipeItem */
|
||||
$recipeItem = array_pop($recipeItems);
|
||||
@ -90,7 +194,7 @@ class CraftingTransaction extends InventoryTransaction{
|
||||
|
||||
$haveCount = 0;
|
||||
foreach($txItems as $j => $txItem){
|
||||
if($txItem->equals($recipeItem, !$wildcards || !$recipeItem->hasAnyDamageValue(), !$wildcards || $recipeItem->hasNamedTag())){
|
||||
if($txItem->canStackWith($recipeItem)){
|
||||
$haveCount += $txItem->getCount();
|
||||
unset($txItems[$j]);
|
||||
}
|
||||
@ -115,7 +219,7 @@ class CraftingTransaction extends InventoryTransaction{
|
||||
|
||||
if(count($txItems) > 0){
|
||||
//all items should be destroyed in this process
|
||||
throw new TransactionValidationException("Expected 0 ingredients left over, have " . count($txItems));
|
||||
throw new TransactionValidationException("Expected 0 items left over, have " . count($txItems));
|
||||
}
|
||||
|
||||
return $iterations;
|
||||
@ -133,9 +237,9 @@ class CraftingTransaction extends InventoryTransaction{
|
||||
foreach($this->craftingManager->matchRecipeByOutputs($this->outputs) as $recipe){
|
||||
try{
|
||||
//compute number of times recipe was crafted
|
||||
$this->repetitions = $this->matchRecipeItems($this->outputs, $recipe->getResultsFor($this->source->getCraftingGrid()), false);
|
||||
$this->repetitions = $this->matchOutputs($this->outputs, $recipe->getResultsFor($this->source->getCraftingGrid()));
|
||||
//assert that $repetitions x recipe ingredients should be consumed
|
||||
$this->matchRecipeItems($this->inputs, $recipe->getIngredientList(), true, $this->repetitions);
|
||||
self::matchIngredients($this->inputs, $recipe->getIngredientList(), $this->repetitions);
|
||||
|
||||
//Success!
|
||||
$this->recipe = $recipe;
|
||||
|
@ -581,7 +581,7 @@ class Item implements \JsonSerializable{
|
||||
}
|
||||
|
||||
final public function __toString() : string{
|
||||
return "Item " . $this->name . " (" . $this->getId() . ":" . ($this->hasAnyDamageValue() ? "?" : $this->getMeta()) . ")x" . $this->count . ($this->hasNamedTag() ? " tags:0x" . base64_encode((new LittleEndianNbtSerializer())->write(new TreeRoot($this->getNamedTag()))) : "");
|
||||
return "Item " . $this->name . " (" . $this->getId() . ":" . $this->getMeta() . ")x" . $this->count . ($this->hasNamedTag() ? " tags:0x" . base64_encode((new LittleEndianNbtSerializer())->write(new TreeRoot($this->getNamedTag()))) : "");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -647,7 +647,7 @@ class Item implements \JsonSerializable{
|
||||
* @param int $slot optional, the inventory slot of the item
|
||||
*/
|
||||
public function nbtSerialize(int $slot = -1) : CompoundTag{
|
||||
return GlobalItemDataHandlers::getSerializer()->serializeStack($this, $slot !== -1 ? $slot : null);
|
||||
return GlobalItemDataHandlers::getSerializer()->serializeStack($this, $slot !== -1 ? $slot : null)->toNbt();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -457,27 +457,30 @@ class ItemFactory{
|
||||
public function get(int $id, int $meta = 0, int $count = 1, ?CompoundTag $tags = null) : Item{
|
||||
/** @var Item|null $item */
|
||||
$item = null;
|
||||
if($meta !== -1){
|
||||
if(isset($this->list[$offset = self::getListOffset($id, $meta)])){
|
||||
$item = clone $this->list[$offset];
|
||||
}elseif(isset($this->list[$zero = self::getListOffset($id, 0)]) && $this->list[$zero] instanceof Durable){
|
||||
if($meta <= $this->list[$zero]->getMaxDurability()){
|
||||
$item = clone $this->list[$zero];
|
||||
$item->setDamage($meta);
|
||||
}else{
|
||||
$item = new Item(new IID($id, $meta));
|
||||
}
|
||||
}elseif($id < 256){ //intentionally includes negatives, for extended block IDs
|
||||
//TODO: do not assume that item IDs and block IDs are the same or related
|
||||
$blockStateData = GlobalBlockStateHandlers::getUpgrader()->upgradeIntIdMeta(self::itemToBlockId($id), $meta & 0xf);
|
||||
if($blockStateData !== null){
|
||||
try{
|
||||
$blockStateId = GlobalBlockStateHandlers::getDeserializer()->deserialize($blockStateData);
|
||||
$item = new ItemBlock(new IID($id, $meta), BlockFactory::getInstance()->fromFullBlock($blockStateId));
|
||||
}catch(BlockStateDeserializeException $e){
|
||||
\GlobalLogger::get()->logException($e);
|
||||
//fallthru
|
||||
}
|
||||
|
||||
if($meta < 0 || $meta > 0x7ffe){ //0x7fff would cause problems with recipe wildcards
|
||||
throw new \InvalidArgumentException("Meta cannot be negative or larger than " . 0x7ffe);
|
||||
}
|
||||
|
||||
if(isset($this->list[$offset = self::getListOffset($id, $meta)])){
|
||||
$item = clone $this->list[$offset];
|
||||
}elseif(isset($this->list[$zero = self::getListOffset($id, 0)]) && $this->list[$zero] instanceof Durable){
|
||||
if($meta <= $this->list[$zero]->getMaxDurability()){
|
||||
$item = clone $this->list[$zero];
|
||||
$item->setDamage($meta);
|
||||
}else{
|
||||
$item = new Item(new IID($id, $meta));
|
||||
}
|
||||
}elseif($id < 256){ //intentionally includes negatives, for extended block IDs
|
||||
//TODO: do not assume that item IDs and block IDs are the same or related
|
||||
$blockStateData = GlobalBlockStateHandlers::getUpgrader()->upgradeIntIdMeta(self::itemToBlockId($id), $meta & 0xf);
|
||||
if($blockStateData !== null){
|
||||
try{
|
||||
$blockStateId = GlobalBlockStateHandlers::getDeserializer()->deserialize($blockStateData);
|
||||
$item = new ItemBlock(new IID($id, $meta), BlockFactory::getInstance()->fromFullBlock($blockStateId));
|
||||
}catch(BlockStateDeserializeException $e){
|
||||
\GlobalLogger::get()->logException($e);
|
||||
//fallthru
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,8 +31,11 @@ class ItemIdentifier{
|
||||
if($id < -0x8000 || $id > 0x7fff){ //signed short range
|
||||
throw new \InvalidArgumentException("ID must be in range " . -0x8000 . " - " . 0x7fff);
|
||||
}
|
||||
if($meta < 0 || $meta > 0x7ffe){
|
||||
throw new \InvalidArgumentException("Meta must be in range 0 - " . 0x7ffe);
|
||||
}
|
||||
$this->id = $id;
|
||||
$this->meta = $meta !== -1 ? $meta & 0x7FFF : -1;
|
||||
$this->meta = $meta;
|
||||
}
|
||||
|
||||
public function getId() : int{
|
||||
|
@ -104,6 +104,9 @@ final class LegacyStringToItemParser{
|
||||
$meta = 0;
|
||||
}elseif(is_numeric($b[1])){
|
||||
$meta = (int) $b[1];
|
||||
if($meta < 0 || $meta > 0x7ffe){
|
||||
throw new LegacyStringToItemParserException("Meta value $meta is outside the range 0 - " . 0x7ffe);
|
||||
}
|
||||
}else{
|
||||
throw new LegacyStringToItemParserException("Unable to parse \"" . $b[1] . "\" from \"" . $input . "\" as a valid meta value");
|
||||
}
|
||||
|
11
src/network/mcpe/cache/CraftingDataCache.php
vendored
11
src/network/mcpe/cache/CraftingDataCache.php
vendored
@ -25,6 +25,7 @@ namespace pocketmine\network\mcpe\cache;
|
||||
|
||||
use pocketmine\crafting\CraftingManager;
|
||||
use pocketmine\crafting\FurnaceType;
|
||||
use pocketmine\crafting\RecipeIngredient;
|
||||
use pocketmine\crafting\ShapelessRecipeType;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\item\ItemFactory;
|
||||
@ -37,7 +38,7 @@ use pocketmine\network\mcpe\protocol\types\recipe\FurnaceRecipe as ProtocolFurna
|
||||
use pocketmine\network\mcpe\protocol\types\recipe\FurnaceRecipeBlockName;
|
||||
use pocketmine\network\mcpe\protocol\types\recipe\PotionContainerChangeRecipe as ProtocolPotionContainerChangeRecipe;
|
||||
use pocketmine\network\mcpe\protocol\types\recipe\PotionTypeRecipe as ProtocolPotionTypeRecipe;
|
||||
use pocketmine\network\mcpe\protocol\types\recipe\RecipeIngredient;
|
||||
use pocketmine\network\mcpe\protocol\types\recipe\RecipeIngredient as ProtocolRecipeIngredient;
|
||||
use pocketmine\network\mcpe\protocol\types\recipe\ShapedRecipe as ProtocolShapedRecipe;
|
||||
use pocketmine\network\mcpe\protocol\types\recipe\ShapelessRecipe as ProtocolShapelessRecipe;
|
||||
use pocketmine\timings\Timings;
|
||||
@ -91,8 +92,8 @@ final class CraftingDataCache{
|
||||
$recipesWithTypeIds[] = new ProtocolShapelessRecipe(
|
||||
CraftingDataPacket::ENTRY_SHAPELESS,
|
||||
Binary::writeInt(++$counter),
|
||||
array_map(function(Item $item) use ($converter) : RecipeIngredient{
|
||||
return $converter->coreItemStackToRecipeIngredient($item);
|
||||
array_map(function(RecipeIngredient $item) use ($converter) : ProtocolRecipeIngredient{
|
||||
return $converter->coreRecipeIngredientToNet($item);
|
||||
}, $recipe->getIngredientList()),
|
||||
array_map(function(Item $item) use ($converter) : ItemStack{
|
||||
return $converter->coreItemStackToNet($item);
|
||||
@ -110,7 +111,7 @@ final class CraftingDataCache{
|
||||
|
||||
for($row = 0, $height = $recipe->getHeight(); $row < $height; ++$row){
|
||||
for($column = 0, $width = $recipe->getWidth(); $column < $width; ++$column){
|
||||
$inputs[$row][$column] = $converter->coreItemStackToRecipeIngredient($recipe->getIngredient($column, $row));
|
||||
$inputs[$row][$column] = $converter->coreRecipeIngredientToNet($recipe->getIngredient($column, $row));
|
||||
}
|
||||
}
|
||||
$recipesWithTypeIds[] = $r = new ProtocolShapedRecipe(
|
||||
@ -136,7 +137,7 @@ final class CraftingDataCache{
|
||||
default => throw new AssumptionFailedError("Unreachable"),
|
||||
};
|
||||
foreach($manager->getFurnaceRecipeManager($furnaceType)->getAll() as $recipe){
|
||||
$input = $converter->coreItemStackToRecipeIngredient($recipe->getInput());
|
||||
$input = $converter->coreRecipeIngredientToNet($recipe->getInput());
|
||||
$recipesWithTypeIds[] = new ProtocolFurnaceRecipe(
|
||||
CraftingDataPacket::ENTRY_FURNACE_DATA,
|
||||
$input->getId(),
|
||||
|
@ -29,6 +29,9 @@ use pocketmine\block\inventory\EnchantInventory;
|
||||
use pocketmine\block\inventory\LoomInventory;
|
||||
use pocketmine\block\inventory\StonecutterInventory;
|
||||
use pocketmine\block\VanillaBlocks;
|
||||
use pocketmine\crafting\ExactRecipeIngredient;
|
||||
use pocketmine\crafting\MetaWildcardRecipeIngredient;
|
||||
use pocketmine\crafting\RecipeIngredient;
|
||||
use pocketmine\inventory\transaction\action\CreateItemAction;
|
||||
use pocketmine\inventory\transaction\action\DestroyItemAction;
|
||||
use pocketmine\inventory\transaction\action\DropItemAction;
|
||||
@ -48,11 +51,12 @@ use pocketmine\network\mcpe\protocol\types\inventory\ContainerIds;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\ItemStack;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\NetworkInventoryAction;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\UIInventorySlotOffset;
|
||||
use pocketmine\network\mcpe\protocol\types\recipe\RecipeIngredient;
|
||||
use pocketmine\network\mcpe\protocol\types\recipe\RecipeIngredient as ProtocolRecipeIngredient;
|
||||
use pocketmine\player\GameMode;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\SingletonTrait;
|
||||
use function get_class;
|
||||
|
||||
class TypeConverter{
|
||||
use SingletonTrait;
|
||||
@ -115,39 +119,40 @@ class TypeConverter{
|
||||
}
|
||||
}
|
||||
|
||||
public function coreItemStackToRecipeIngredient(Item $itemStack) : RecipeIngredient{
|
||||
if($itemStack->isNull()){
|
||||
return new RecipeIngredient(0, 0, 0);
|
||||
public function coreRecipeIngredientToNet(?RecipeIngredient $ingredient) : ProtocolRecipeIngredient{
|
||||
if($ingredient === null){
|
||||
return new ProtocolRecipeIngredient(0, 0, 0);
|
||||
}
|
||||
if($itemStack->hasAnyDamageValue()){
|
||||
[$id, ] = ItemTranslator::getInstance()->toNetworkId(ItemFactory::getInstance()->get($itemStack->getId()));
|
||||
if($ingredient instanceof MetaWildcardRecipeIngredient){
|
||||
$id = GlobalItemTypeDictionary::getInstance()->getDictionary()->fromStringId($ingredient->getItemId());
|
||||
$meta = self::RECIPE_INPUT_WILDCARD_META;
|
||||
}else{
|
||||
[$id, $meta] = ItemTranslator::getInstance()->toNetworkId($itemStack);
|
||||
}elseif($ingredient instanceof ExactRecipeIngredient){
|
||||
$item = $ingredient->getItem();
|
||||
[$id, $meta] = ItemTranslator::getInstance()->toNetworkId($item);
|
||||
if($id < 256){
|
||||
//TODO: this is needed for block crafting recipes to work - we need to replace this with some kind of
|
||||
//blockstate <-> meta mapping table so that we can remove the legacy code from the core
|
||||
$meta = $itemStack->getMeta();
|
||||
$meta = $item->getMeta();
|
||||
}
|
||||
}else{
|
||||
throw new \LogicException("Unsupported recipe ingredient type " . get_class($ingredient) . ", only " . ExactRecipeIngredient::class . " and " . MetaWildcardRecipeIngredient::class . " are supported");
|
||||
}
|
||||
return new RecipeIngredient($id, $meta, $itemStack->getCount());
|
||||
return new ProtocolRecipeIngredient($id, $meta, 1);
|
||||
}
|
||||
|
||||
public function recipeIngredientToCoreItemStack(RecipeIngredient $ingredient) : Item{
|
||||
public function netRecipeIngredientToCore(ProtocolRecipeIngredient $ingredient) : ?RecipeIngredient{
|
||||
if($ingredient->getId() === 0){
|
||||
return VanillaItems::AIR();
|
||||
return null;
|
||||
}
|
||||
|
||||
if($ingredient->getMeta() === self::RECIPE_INPUT_WILDCARD_META){
|
||||
$itemId = GlobalItemTypeDictionary::getInstance()->getDictionary()->fromIntId($ingredient->getId());
|
||||
return new MetaWildcardRecipeIngredient($itemId);
|
||||
}
|
||||
|
||||
//TODO: this won't be handled properly for blockitems because a block runtimeID is expected rather than a meta value
|
||||
|
||||
if($ingredient->getMeta() === self::RECIPE_INPUT_WILDCARD_META){
|
||||
$idItem = ItemTranslator::getInstance()->fromNetworkId($ingredient->getId(), 0, 0);
|
||||
$result = ItemFactory::getInstance()->get($idItem->getId(), -1);
|
||||
}else{
|
||||
$result = ItemTranslator::getInstance()->fromNetworkId($ingredient->getId(), $ingredient->getMeta(), 0);
|
||||
}
|
||||
$result->setCount($ingredient->getCount());
|
||||
return $result;
|
||||
$result = ItemTranslator::getInstance()->fromNetworkId($ingredient->getId(), $ingredient->getMeta(), 0);
|
||||
return new ExactRecipeIngredient($result);
|
||||
}
|
||||
|
||||
public function coreItemStackToNet(Item $itemStack) : ItemStack{
|
||||
@ -237,8 +242,8 @@ class TypeConverter{
|
||||
if($id !== null && ($id < -0x8000 || $id >= 0x7fff)){
|
||||
throw new TypeConversionException("Item ID must be in range " . -0x8000 . " ... " . 0x7fff . " (received $id)");
|
||||
}
|
||||
if($meta < 0 || $meta >= 0x7fff){ //this meta value may have been restored from the NBT
|
||||
throw new TypeConversionException("Item meta must be in range 0 ... " . 0x7fff . " (received $meta)");
|
||||
if($meta < 0 || $meta >= 0x7ffe){ //this meta value may have been restored from the NBT
|
||||
throw new TypeConversionException("Item meta must be in range 0 ... " . 0x7ffe . " (received $meta)");
|
||||
}
|
||||
$itemResult = ItemFactory::getInstance()->get($id ?? $itemResult->getId(), $meta);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user