diff --git a/src/block/inventory/SmithingTableInventory.php b/src/block/inventory/SmithingTableInventory.php index 2f67ac9d2dd..3408435694c 100644 --- a/src/block/inventory/SmithingTableInventory.php +++ b/src/block/inventory/SmithingTableInventory.php @@ -30,6 +30,10 @@ final class SmithingTableInventory extends SimpleInventory implements BlockInventory, TemporaryInventory{ use BlockInventoryTrait; + public const SLOT_INPUT = 0; + public const SLOT_ADDITION = 1; + public const SLOT_TEMPLATE = 2; + public function __construct(Position $holder){ $this->holder = $holder; parent::__construct(3); diff --git a/src/crafting/CraftingManager.php b/src/crafting/CraftingManager.php index 93d6e1838fc..e5b890545a7 100644 --- a/src/crafting/CraftingManager.php +++ b/src/crafting/CraftingManager.php @@ -68,6 +68,12 @@ class CraftingManager{ */ protected array $potionTypeRecipes = []; + /** + * @var SmithingRecipe[] + * @phpstan-var list + */ + protected array $smithingRecipes = []; + /** * @var PotionContainerChangeRecipe[] * @phpstan-var list @@ -187,6 +193,18 @@ public function getPotionContainerChangeRecipes() : array{ return $this->potionContainerChangeRecipes; } + /** + * @return SmithingRecipe[] + * @phpstan-return list + */ + public function getSmithingRecipes() : array{ + return $this->smithingRecipes; + } + + public function getSmithingRecipeFromIndex(int $index) : ?SmithingRecipe{ + return $this->smithingRecipes[$index] ?? null; + } + public function registerShapedRecipe(ShapedRecipe $recipe) : void{ $this->shapedRecipes[self::hashOutputs($recipe->getResults())][] = $recipe; $this->craftingRecipeIndex[] = $recipe; @@ -221,6 +239,14 @@ public function registerPotionContainerChangeRecipe(PotionContainerChangeRecipe } } + public function registerSmithingRecipe(SmithingRecipe $recipe) : void{ + $this->smithingRecipes[] = $recipe; + + foreach($this->recipeRegisteredCallbacks as $callback){ + $callback(); + } + } + /** * @param Item[] $outputs */ diff --git a/src/crafting/CraftingManagerFromDataHelper.php b/src/crafting/CraftingManagerFromDataHelper.php index 7c9cdd58b4d..8b86eff0b1c 100644 --- a/src/crafting/CraftingManagerFromDataHelper.php +++ b/src/crafting/CraftingManagerFromDataHelper.php @@ -30,6 +30,8 @@ use pocketmine\crafting\json\RecipeIngredientData; use pocketmine\crafting\json\ShapedRecipeData; use pocketmine\crafting\json\ShapelessRecipeData; +use pocketmine\crafting\json\SmithingTransformRecipeData; +use pocketmine\crafting\json\SmithingTrimRecipeData; use pocketmine\data\bedrock\block\BlockStateData; use pocketmine\data\bedrock\item\BlockItemIdMap; use pocketmine\data\bedrock\item\ItemTypeDeserializeException; @@ -332,9 +334,27 @@ public static function make(string $directoryPath) : CraftingManager{ $outputId )); } + foreach(self::loadJsonArrayOfObjectsFile(Path::join($directoryPath, 'smithing.json'), SmithingTransformRecipeData::class) as $recipe){ + $input = self::deserializeIngredient($recipe->input); + $template = self::deserializeIngredient($recipe->template); + $addition = self::deserializeIngredient($recipe->addition); + $output = self::deserializeItemStack($recipe->output); - //TODO: smithing + if($input === null || $template === null || $addition === null || $output === null){ + continue; + } + $result->registerSmithingRecipe(new SmithingTransformRecipe($input, $addition, $template, $output)); + } + foreach(self::loadJsonArrayOfObjectsFile(Path::join($directoryPath, 'smithing_trim.json'), SmithingTrimRecipeData::class) as $recipe){ + $input = self::deserializeIngredient($recipe->input); + $addition = self::deserializeIngredient($recipe->addition); + $template = self::deserializeIngredient($recipe->template); + if($input === null || $template === null || $addition === null){ + continue; + } + $result->registerSmithingRecipe(new SmithingTrimRecipe($input, $addition, $template)); + } return $result; } } diff --git a/src/crafting/SmithingRecipe.php b/src/crafting/SmithingRecipe.php new file mode 100644 index 00000000000..53245ac73a1 --- /dev/null +++ b/src/crafting/SmithingRecipe.php @@ -0,0 +1,40 @@ +result = clone $this->result; + } + + public function getInput() : RecipeIngredient{ + return $this->input; + } + + public function getAddition() : RecipeIngredient{ + return $this->addition; + } + + public function getTemplate() : RecipeIngredient{ + return $this->template; + } + + public function getResult() : Item{ + return clone $this->result; + } + + /** + * @param Item[] $inputs + */ + public function getResultFor(array $inputs) : ?Item{ + foreach($inputs as $item){ + if($this->input->accepts($item)){ + return $this->getResult()->setNamedTag($item->getNamedTag()); + } + } + return null; + } +} diff --git a/src/crafting/SmithingTrimRecipe.php b/src/crafting/SmithingTrimRecipe.php new file mode 100644 index 00000000000..e75dc4c22e7 --- /dev/null +++ b/src/crafting/SmithingTrimRecipe.php @@ -0,0 +1,76 @@ +input; + } + + public function getAddition() : RecipeIngredient{ + return $this->addition; + } + + public function getTemplate() : RecipeIngredient{ + return $this->template; + } + + /** + * @param Item[] $inputs + */ + public function getResultFor(array $inputs) : ?Item{ + $input = $template = $addition = null; + foreach($inputs as $item){ + if($this->input->accepts($item) && $item instanceof Armor){ + $input = $item; + }elseif($this->addition->accepts($item)){ + $addition = $item; + }elseif($this->template->accepts($item)){ + $template = $item; + } + } + if($input !== null && $addition !== null && $template !== null){ + $material = ArmorTrimMaterialTypeIdMap::getInstance()->fromItem($addition); + $pattern = ArmorTrimPatternTypeIdMap::getInstance()->fromItem($template); + if($material !== null && $pattern !== null){ + return (clone $input)->setTrim(new ArmorTrim($material, $pattern)); + } + } + + return null; + } +} diff --git a/src/data/bedrock/ArmorTrimMaterialTypeIdMap.php b/src/data/bedrock/ArmorTrimMaterialTypeIdMap.php new file mode 100644 index 00000000000..1a07f51fce2 --- /dev/null +++ b/src/data/bedrock/ArmorTrimMaterialTypeIdMap.php @@ -0,0 +1,103 @@ + + */ + private array $idToMaterial = []; + /** + * @var ArmorTrimMaterial[] + * @phpstan-var array + */ + private array $itemToMaterial = []; + /** + * @var string[] + * @phpstan-var array + */ + private array $materialToId = []; + + public function __construct(){ + foreach(Materials::getAll() as $material){ + $this->register(match($material){ + Materials::AMETHYST() => Ids::AMETHYST, + Materials::COPPER() => Ids::COPPER, + Materials::DIAMOND() => Ids::DIAMOND, + Materials::EMERALD() => Ids::EMERALD, + Materials::GOLD() => Ids::GOLD, + Materials::IRON() => Ids::IRON, + Materials::LAPIS() => Ids::LAPIS, + Materials::NETHERITE() => Ids::NETHERITE, + Materials::QUARTZ() => Ids::QUARTZ, + Materials::REDSTONE() => Ids::REDSTONE, + Materials::RESIN() => Ids::RESIN, + default => throw new AssumptionFailedError("Unhandled armor trim material type") + }, $material); + } + } + + public function register(string $stringId, ArmorTrimMaterial $material) : void{ + $this->idToMaterial[$stringId] = $material; + $this->itemToMaterial[$material->getItem()->getStateId()] = $material; + $this->materialToId[spl_object_id($material)] = $stringId; + } + + public function fromId(string $id) : ?ArmorTrimMaterial{ + return $this->idToMaterial[$id] ?? null; + } + + public function fromItem(Item $item) : ?ArmorTrimMaterial{ + return $this->itemToMaterial[$item->getStateId()] ?? null; + } + + public function toId(ArmorTrimMaterial $material) : string{ + $k = spl_object_id($material); + if(!array_key_exists($k, $this->materialToId)){ + throw new \InvalidArgumentException("Missing mapping for armor trim material with item" . $material->getItem()->getName() . " and color " . $material->getColor()); + } + return $this->materialToId[$k]; + } + + /** + * @return ArmorTrimMaterial[] + * @phpstan-return list + */ + public function getAllMaterials() : array{ + return array_values($this->idToMaterial); + } +} diff --git a/src/data/bedrock/ArmorTrimMaterialTypeIds.php b/src/data/bedrock/ArmorTrimMaterialTypeIds.php new file mode 100644 index 00000000000..a36f65f4d69 --- /dev/null +++ b/src/data/bedrock/ArmorTrimMaterialTypeIds.php @@ -0,0 +1,38 @@ + + */ + private array $idToPattern = []; + /** + * @var ArmorTrimPattern[] + * @phpstan-var array + */ + private array $itemToPattern = []; + /** + * @var string[] + * @phpstan-var array + */ + private array $patternToId = []; + + public function __construct(){ + foreach(Patterns::getAll() as $pattern){ + $this->register(match($pattern){ + Patterns::COAST() => Ids::COAST, + Patterns::DUNE() => Ids::DUNE, + Patterns::EYE() => Ids::EYE, + Patterns::HOST() => Ids::HOST, + Patterns::RAISER() => Ids::RAISER, + Patterns::RIB() => Ids::RIB, + Patterns::SENTRY() => Ids::SENTRY, + Patterns::SHAPER() => Ids::SHAPER, + Patterns::SILENCE() => Ids::SILENCE, + Patterns::SNOUT() => Ids::SNOUT, + Patterns::SPIRE() => Ids::SPIRE, + Patterns::TIDE() => Ids::TIDE, + Patterns::VEX() => Ids::VEX, + Patterns::WARD() => Ids::WARD, + Patterns::WAYFINDER() => Ids::WAYFINDER, + Patterns::WILD() => Ids::WILD, + default => throw new AssumptionFailedError("Unhandled armor trim pattern type") + }, $pattern); + } + } + + public function register(string $stringId, ArmorTrimPattern $pattern) : void{ + $this->idToPattern[$stringId] = $pattern; + $this->itemToPattern[$pattern->getItem()->getStateId()] = $pattern; + $this->patternToId[spl_object_id($pattern)] = $stringId; + } + + public function fromId(string $id) : ?ArmorTrimPattern{ + return $this->idToPattern[$id] ?? null; + } + + public function fromItem(Item $item) : ?ArmorTrimPattern{ + return $this->itemToPattern[$item->getStateId()] ?? null; + } + + public function toId(ArmorTrimPattern $pattern) : string{ + $k = spl_object_id($pattern); + if(!array_key_exists($k, $this->patternToId)){ + throw new \InvalidArgumentException("Missing mapping for armor trim pattern with item" . $pattern->getItem()->getName()); + } + return $this->patternToId[$k]; + } + + /** + * @return ArmorTrimPattern[] + * @phpstan-return list + */ + public function getAllPatterns() : array{ + return array_values($this->idToPattern); + } +} diff --git a/src/data/bedrock/ArmorTrimPatternTypeIds.php b/src/data/bedrock/ArmorTrimPatternTypeIds.php new file mode 100644 index 00000000000..877c9c536cb --- /dev/null +++ b/src/data/bedrock/ArmorTrimPatternTypeIds.php @@ -0,0 +1,43 @@ +actions) < 1){ + throw new TransactionValidationException("Transaction must have at least one action to be executable"); + } + + $inputs = []; + $outputs = []; + $this->matchItems($outputs, $inputs); + + if(($inputCount = count($inputs)) !== 3){ + throw new TransactionValidationException("Expected 3 input items, got $inputCount"); + } + if(($outputCount = count($outputs)) !== 1){ + throw new TransactionValidationException("Expected 1 output item, but received $outputCount"); + } + if(($output = $this->recipe->getResultFor($inputs)) === null){ + throw new TransactionValidationException("Couldn't find a matching output item for the given inputs"); + } + if(!$output->equalsExact($outputs[0])){ + throw new TransactionValidationException("Invalid output item"); + } + } +} diff --git a/src/item/Armor.php b/src/item/Armor.php index 63a8003adc9..2e6117758c9 100644 --- a/src/item/Armor.php +++ b/src/item/Armor.php @@ -24,6 +24,8 @@ namespace pocketmine\item; use pocketmine\color\Color; +use pocketmine\data\bedrock\ArmorTrimMaterialTypeIdMap; +use pocketmine\data\bedrock\ArmorTrimPatternTypeIdMap; use pocketmine\event\entity\EntityDamageEvent; use pocketmine\inventory\ArmorInventory; use pocketmine\item\enchantment\ProtectionEnchantment; @@ -40,8 +42,14 @@ class Armor extends Durable{ public const TAG_CUSTOM_COLOR = "customColor"; //TAG_Int + private const TAG_TRIM = "Trim"; // TAG_Compound + private const TAG_TRIM_MATERIAL = "Material"; //TAG_String + private const TAG_TRIM_PATTERN = "Pattern"; //TAG_String + private ArmorTypeInfo $armorInfo; + private ?ArmorTrim $armorTrim = null; + protected ?Color $customColor = null; /** @@ -106,6 +114,16 @@ public function clearCustomColor() : self{ return $this; } + public function getTrim() : ?ArmorTrim{ + return $this->armorTrim; + } + + /** @return $this */ + public function setTrim(?ArmorTrim $trim) : self{ + $this->armorTrim = $trim; + return $this; + } + /** * Returns the total enchantment protection factor this armour piece offers from all applicable protection * enchantments on the item. @@ -164,6 +182,16 @@ protected function deserializeCompoundTag(CompoundTag $tag) : void{ }else{ $this->customColor = null; } + + $trimTag = $tag->getTag(self::TAG_TRIM); + if($trimTag instanceof CompoundTag){ + $material = ArmorTrimMaterialTypeIdMap::getInstance()->fromId($trimTag->getString(self::TAG_TRIM_MATERIAL)); + $pattern = ArmorTrimPatternTypeIdMap::getInstance()->fromId($trimTag->getString(self::TAG_TRIM_PATTERN)); + + if($material !== null && $pattern !== null){ + $this->armorTrim = new ArmorTrim($material, $pattern); + } + } } protected function serializeCompoundTag(CompoundTag $tag) : void{ @@ -171,5 +199,11 @@ protected function serializeCompoundTag(CompoundTag $tag) : void{ $this->customColor !== null ? $tag->setInt(self::TAG_CUSTOM_COLOR, Binary::signInt($this->customColor->toARGB())) : $tag->removeTag(self::TAG_CUSTOM_COLOR); + + $this->armorTrim !== null ? + $tag->setTag(self::TAG_TRIM, CompoundTag::create() + ->setString(self::TAG_TRIM_MATERIAL, ArmorTrimMaterialTypeIdMap::getInstance()->toId($this->armorTrim->getMaterial())) + ->setString(self::TAG_TRIM_PATTERN, ArmorTrimPatternTypeIdMap::getInstance()->toId($this->armorTrim->getPattern()))) : + $tag->removeTag(self::TAG_TRIM); } } diff --git a/src/item/ArmorTrim.php b/src/item/ArmorTrim.php new file mode 100644 index 00000000000..caadfd935b7 --- /dev/null +++ b/src/item/ArmorTrim.php @@ -0,0 +1,40 @@ +material; + } + + public function getPattern() : ArmorTrimPattern{ + return $this->pattern; + } +} diff --git a/src/item/ArmorTrimMaterial.php b/src/item/ArmorTrimMaterial.php new file mode 100644 index 00000000000..777f76417d4 --- /dev/null +++ b/src/item/ArmorTrimMaterial.php @@ -0,0 +1,55 @@ +item = clone $item; + } + + public function getItem() : Item{ + return clone $this->item; + } + + /** + * Returns the Minecraft color code. + */ + public function getColor() : string{ + return $this->color; + } +} diff --git a/src/item/ArmorTrimPattern.php b/src/item/ArmorTrimPattern.php new file mode 100644 index 00000000000..53bbc0b2712 --- /dev/null +++ b/src/item/ArmorTrimPattern.php @@ -0,0 +1,42 @@ +item = clone $item; + } + + public function getItem() : Item{ + return clone $this->item; + } +} diff --git a/src/item/VanillaArmorTrimMaterials.php b/src/item/VanillaArmorTrimMaterials.php new file mode 100644 index 00000000000..458d826f1e0 --- /dev/null +++ b/src/item/VanillaArmorTrimMaterials.php @@ -0,0 +1,82 @@ + + */ + public static function getAll() : array{ + // phpstan doesn't support generic traits yet :( + /** @var ArmorTrimMaterial[] $result */ + $result = self::_registryGetAll(); + return $result; + } + + protected static function setup() : void{ + self::register("amethyst", new ArmorTrimMaterial(VanillaItems::AMETHYST_SHARD(), TextFormat::MATERIAL_AMETHYST)); + self::register("copper", new ArmorTrimMaterial(VanillaItems::COPPER_INGOT(), TextFormat::MATERIAL_COPPER)); + self::register("diamond", new ArmorTrimMaterial(VanillaItems::DIAMOND(), TextFormat::MATERIAL_DIAMOND)); + self::register("emerald", new ArmorTrimMaterial(VanillaItems::EMERALD(), TextFormat::MATERIAL_EMERALD)); + self::register("gold", new ArmorTrimMaterial(VanillaItems::GOLD_INGOT(), TextFormat::MATERIAL_GOLD)); + self::register("iron", new ArmorTrimMaterial(VanillaItems::IRON_INGOT(), TextFormat::MATERIAL_IRON)); + self::register("lapis", new ArmorTrimMaterial(VanillaItems::LAPIS_LAZULI(), TextFormat::MATERIAL_LAPIS)); + self::register("netherite", new ArmorTrimMaterial(VanillaItems::NETHERITE_INGOT(), TextFormat::MATERIAL_NETHERITE)); + self::register("quartz", new ArmorTrimMaterial(VanillaItems::NETHER_QUARTZ(), TextFormat::MATERIAL_QUARTZ)); + self::register("redstone", new ArmorTrimMaterial(VanillaItems::REDSTONE_DUST(), TextFormat::MATERIAL_REDSTONE)); + self::register("resin", new ArmorTrimMaterial(VanillaItems::RESIN_BRICK(), TextFormat::MATERIAL_RESIN)); + } +} diff --git a/src/item/VanillaArmorTrimPatterns.php b/src/item/VanillaArmorTrimPatterns.php new file mode 100644 index 00000000000..3045ce84126 --- /dev/null +++ b/src/item/VanillaArmorTrimPatterns.php @@ -0,0 +1,91 @@ + + */ + public static function getAll() : array{ + // phpstan doesn't support generic traits yet :( + /** @var ArmorTrimPattern[] $result */ + $result = self::_registryGetAll(); + return $result; + } + + protected static function setup() : void{ + self::register("coast", new ArmorTrimPattern(VanillaItems::COAST_ARMOR_TRIM_SMITHING_TEMPLATE())); + self::register("dune", new ArmorTrimPattern(VanillaItems::DUNE_ARMOR_TRIM_SMITHING_TEMPLATE())); + self::register("eye", new ArmorTrimPattern(VanillaItems::EYE_ARMOR_TRIM_SMITHING_TEMPLATE())); + self::register("host", new ArmorTrimPattern(VanillaItems::HOST_ARMOR_TRIM_SMITHING_TEMPLATE())); + self::register("raiser", new ArmorTrimPattern(VanillaItems::RAISER_ARMOR_TRIM_SMITHING_TEMPLATE())); + self::register("rib", new ArmorTrimPattern(VanillaItems::RIB_ARMOR_TRIM_SMITHING_TEMPLATE())); + self::register("sentry", new ArmorTrimPattern(VanillaItems::SENTRY_ARMOR_TRIM_SMITHING_TEMPLATE())); + self::register("shaper", new ArmorTrimPattern(VanillaItems::SHAPER_ARMOR_TRIM_SMITHING_TEMPLATE())); + self::register("silence", new ArmorTrimPattern(VanillaItems::SILENCE_ARMOR_TRIM_SMITHING_TEMPLATE())); + self::register("snout", new ArmorTrimPattern(VanillaItems::SNOUT_ARMOR_TRIM_SMITHING_TEMPLATE())); + self::register("spire", new ArmorTrimPattern(VanillaItems::SPIRE_ARMOR_TRIM_SMITHING_TEMPLATE())); + self::register("tide", new ArmorTrimPattern(VanillaItems::TIDE_ARMOR_TRIM_SMITHING_TEMPLATE())); + self::register("vex", new ArmorTrimPattern(VanillaItems::VEX_ARMOR_TRIM_SMITHING_TEMPLATE())); + self::register("ward", new ArmorTrimPattern(VanillaItems::WARD_ARMOR_TRIM_SMITHING_TEMPLATE())); + self::register("wayfinder", new ArmorTrimPattern(VanillaItems::WAYFINDER_ARMOR_TRIM_SMITHING_TEMPLATE())); + self::register("wild", new ArmorTrimPattern(VanillaItems::WILD_ARMOR_TRIM_SMITHING_TEMPLATE())); + } +} diff --git a/src/network/mcpe/InventoryManager.php b/src/network/mcpe/InventoryManager.php index 01368ec0a85..01941082810 100644 --- a/src/network/mcpe/InventoryManager.php +++ b/src/network/mcpe/InventoryManager.php @@ -81,6 +81,12 @@ * @phpstan-type ContainerOpenClosure \Closure(int $id, Inventory $inventory) : (list|null) */ class InventoryManager{ + + //TODO: Hack! In Bedrock these indexes go together with regular crafting recipes, + //so we make offsets to prevent conflicts. + public const ENCHANTING_OPTION_NETWORK_OFFSET = 100000; + public const SMITHING_RECIPE_NETWORK_OFFSET = 200000; + /** * @var InventoryManagerEntry[] spl_object_id(Inventory) => InventoryManagerEntry * @phpstan-var array @@ -119,7 +125,7 @@ class InventoryManager{ private array $enchantingTableOptions = []; //TODO: this should be based on the total number of crafting recipes - if there are ever 100k recipes, this will //conflict with regular recipes - private int $nextEnchantingTableOptionId = 100000; + private int $nextEnchantingTableOptionId = self::ENCHANTING_OPTION_NETWORK_OFFSET; public function __construct( private Player $player, diff --git a/src/network/mcpe/cache/CraftingDataCache.php b/src/network/mcpe/cache/CraftingDataCache.php index da0f37c4445..3da8811ebc5 100644 --- a/src/network/mcpe/cache/CraftingDataCache.php +++ b/src/network/mcpe/cache/CraftingDataCache.php @@ -28,7 +28,10 @@ use pocketmine\crafting\ShapedRecipe; use pocketmine\crafting\ShapelessRecipe; use pocketmine\crafting\ShapelessRecipeType; +use pocketmine\crafting\SmithingTransformRecipe; +use pocketmine\crafting\SmithingTrimRecipe; use pocketmine\network\mcpe\convert\TypeConverter; +use pocketmine\network\mcpe\InventoryManager; use pocketmine\network\mcpe\protocol\CraftingDataPacket; use pocketmine\network\mcpe\protocol\types\recipe\CraftingRecipeBlockName; use pocketmine\network\mcpe\protocol\types\recipe\FurnaceRecipe as ProtocolFurnaceRecipe; @@ -39,6 +42,8 @@ use pocketmine\network\mcpe\protocol\types\recipe\RecipeUnlockingRequirement; use pocketmine\network\mcpe\protocol\types\recipe\ShapedRecipe as ProtocolShapedRecipe; use pocketmine\network\mcpe\protocol\types\recipe\ShapelessRecipe as ProtocolShapelessRecipe; +use pocketmine\network\mcpe\protocol\types\recipe\SmithingTransformRecipe as ProtocolSmithingTransformRecipe; +use pocketmine\network\mcpe\protocol\types\recipe\SmithingTrimRecipe as ProtocolSmithingTrimRecipe; use pocketmine\timings\Timings; use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\Binary; @@ -156,6 +161,33 @@ private function buildCraftingDataCache(CraftingManager $manager) : CraftingData } } + $index = InventoryManager::SMITHING_RECIPE_NETWORK_OFFSET; + foreach($manager->getSmithingRecipes() as $recipe){ + if($recipe instanceof SmithingTransformRecipe){ + $recipesWithTypeIds[] = new ProtocolSmithingTransformRecipe( + CraftingDataPacket::ENTRY_SMITHING_TRANSFORM, + Binary::writeInt($index), + $converter->coreRecipeIngredientToNet($recipe->getTemplate()), + $converter->coreRecipeIngredientToNet($recipe->getInput()), + $converter->coreRecipeIngredientToNet($recipe->getAddition()), + $converter->coreItemStackToNet($recipe->getResult()), + CraftingRecipeBlockName::SMITHING_TABLE, + $index + ); + }elseif($recipe instanceof SmithingTrimRecipe){ + $recipesWithTypeIds[] = new ProtocolSmithingTrimRecipe( + CraftingDataPacket::ENTRY_SMITHING_TRIM, + Binary::writeInt($index), + $converter->coreRecipeIngredientToNet($recipe->getTemplate()), + $converter->coreRecipeIngredientToNet($recipe->getInput()), + $converter->coreRecipeIngredientToNet($recipe->getAddition()), + CraftingRecipeBlockName::SMITHING_TABLE, + $index + ); + } + $index++; + } + $potionTypeRecipes = []; foreach($manager->getPotionTypeRecipes() as $recipe){ $input = $converter->coreRecipeIngredientToNet($recipe->getInput())->getDescriptor(); diff --git a/src/network/mcpe/handler/ItemStackRequestExecutor.php b/src/network/mcpe/handler/ItemStackRequestExecutor.php index 4eddf3100ae..f190bb8687c 100644 --- a/src/network/mcpe/handler/ItemStackRequestExecutor.php +++ b/src/network/mcpe/handler/ItemStackRequestExecutor.php @@ -24,6 +24,7 @@ namespace pocketmine\network\mcpe\handler; use pocketmine\block\inventory\EnchantInventory; +use pocketmine\block\inventory\SmithingTableInventory; use pocketmine\inventory\Inventory; use pocketmine\inventory\transaction\action\CreateItemAction; use pocketmine\inventory\transaction\action\DestroyItemAction; @@ -31,6 +32,7 @@ use pocketmine\inventory\transaction\CraftingTransaction; use pocketmine\inventory\transaction\EnchantingTransaction; use pocketmine\inventory\transaction\InventoryTransaction; +use pocketmine\inventory\transaction\SmithingTransaction; use pocketmine\inventory\transaction\TransactionBuilder; use pocketmine\inventory\transaction\TransactionBuilderInventory; use pocketmine\item\Durable; @@ -295,7 +297,7 @@ protected function takeCreatedItem(int $count) : Item{ * @throws ItemStackRequestProcessException */ private function assertDoingCrafting() : void{ - if(!$this->specialTransaction instanceof CraftingTransaction && !$this->specialTransaction instanceof EnchantingTransaction){ + if(!$this->specialTransaction instanceof CraftingTransaction && !$this->specialTransaction instanceof EnchantingTransaction && !$this->specialTransaction instanceof SmithingTransaction){ if($this->specialTransaction === null){ throw new ItemStackRequestProcessException("Expected CraftRecipe or CraftRecipeAuto action to precede this action"); }else{ @@ -348,6 +350,13 @@ protected function processItemStackRequestAction(ItemStackRequestAction $action) $this->specialTransaction = new EnchantingTransaction($this->player, $option, $optionId + 1); $this->setNextCreatedItem($window->getOutput($optionId)); } + }elseif($window instanceof SmithingTableInventory){ + $craftingManager = $this->player->getServer()->getCraftingManager(); + $recipe = $craftingManager->getSmithingRecipeFromIndex($action->getRecipeId() - InventoryManager::SMITHING_RECIPE_NETWORK_OFFSET); + if($recipe !== null){ + $this->specialTransaction = new SmithingTransaction($this->player, $recipe); + $this->setNextCreatedItem($recipe->getResultFor($window->getContents())); + } }else{ $this->beginCrafting($action->getRecipeId(), $action->getRepetitions()); } diff --git a/src/network/mcpe/handler/PreSpawnPacketHandler.php b/src/network/mcpe/handler/PreSpawnPacketHandler.php index 4aa8be2274b..92ba80eef39 100644 --- a/src/network/mcpe/handler/PreSpawnPacketHandler.php +++ b/src/network/mcpe/handler/PreSpawnPacketHandler.php @@ -23,6 +23,10 @@ namespace pocketmine\network\mcpe\handler; +use pocketmine\data\bedrock\ArmorTrimMaterialTypeIdMap; +use pocketmine\data\bedrock\ArmorTrimPatternTypeIdMap; +use pocketmine\item\ArmorTrimMaterial; +use pocketmine\item\ArmorTrimPattern; use pocketmine\nbt\tag\CompoundTag; use pocketmine\network\mcpe\cache\CraftingDataCache; use pocketmine\network\mcpe\cache\StaticPacketCache; @@ -32,6 +36,7 @@ use pocketmine\network\mcpe\protocol\PlayerAuthInputPacket; use pocketmine\network\mcpe\protocol\RequestChunkRadiusPacket; use pocketmine\network\mcpe\protocol\StartGamePacket; +use pocketmine\network\mcpe\protocol\TrimDataPacket; use pocketmine\network\mcpe\protocol\types\BlockPosition; use pocketmine\network\mcpe\protocol\types\BoolGameRule; use pocketmine\network\mcpe\protocol\types\CacheableNbt; @@ -41,11 +46,15 @@ use pocketmine\network\mcpe\protocol\types\NetworkPermissions; use pocketmine\network\mcpe\protocol\types\PlayerMovementSettings; use pocketmine\network\mcpe\protocol\types\SpawnSettings; +use pocketmine\network\mcpe\protocol\types\TrimMaterial; +use pocketmine\network\mcpe\protocol\types\TrimPattern; use pocketmine\player\Player; use pocketmine\Server; use pocketmine\timings\Timings; use pocketmine\VersionInfo; +use pocketmine\world\format\io\GlobalItemDataHandlers; use Ramsey\Uuid\Uuid; +use function array_map; use function sprintf; /** @@ -148,6 +157,17 @@ public function setUp() : void{ $this->session->getLogger()->debug("Sending creative inventory data"); $this->inventoryManager->syncCreative(); + $this->session->getLogger()->debug("Sending armor trim data"); + + $serializer = GlobalItemDataHandlers::getSerializer(); + $patternMap = ArmorTrimPatternTypeIdMap::getInstance(); + $materialMap = ArmorTrimMaterialTypeIdMap::getInstance(); + + $this->session->sendDataPacket(TrimDataPacket::create( + array_map(fn(ArmorTrimPattern $pattern) => new TrimPattern($serializer->serializeType($pattern->getItem())->getName(), $patternMap->toId($pattern)), $patternMap->getAllPatterns()), + array_map(fn(ArmorTrimMaterial $material) => new TrimMaterial($materialMap->toId($material), $material->getColor(), $serializer->serializeType($material->getItem())->getName()), $materialMap->getAllMaterials())) + ); + $this->session->getLogger()->debug("Sending crafting data"); $this->session->sendDataPacket(CraftingDataCache::getInstance()->getCache($this->server->getCraftingManager()));