Skip to content

Implement anvil #6418

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 21 commits into
base: minor-next
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/block/inventory/AnvilInventory.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

use pocketmine\inventory\SimpleInventory;
use pocketmine\inventory\TemporaryInventory;
use pocketmine\item\Item;
use pocketmine\world\Position;

class AnvilInventory extends SimpleInventory implements BlockInventory, TemporaryInventory{
Expand All @@ -37,4 +38,12 @@ public function __construct(Position $holder){
$this->holder = $holder;
parent::__construct(2);
}

public function getInput() : Item {
return $this->getItem(self::SLOT_INPUT);
}

public function getMaterial() : Item {
return $this->getItem(self::SLOT_MATERIAL);
}
}
66 changes: 66 additions & 0 deletions src/block/utils/AnvilHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?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\block\utils;

use pocketmine\crafting\AnvilCraftResult;
use pocketmine\item\Item;
use pocketmine\Server;

final class AnvilHelper{
private const COST_LIMIT = 39;

/**
* Attempts to calculate the result of an anvil operation.
*
* Returns null if the operation can't do anything.
*/
public static function calculateResult(Item $base, Item $material, ?string $customName, bool $isCreative) : ?AnvilCraftResult{

$recipe = Server::getInstance()->getCraftingManager()->matchAnvilRecipe($base, $material);
if($recipe === null){
return null;
}
$result = $recipe->getResultFor($base, $material);

if($result !== null){
$resultItem = $result->getOutput();
$xpCost = $result->getXpCost();
if(($customName === null || $customName === "") && $resultItem->hasCustomName()){
$xpCost++;
$resultItem->clearCustomName();
}elseif($customName !== null && $resultItem->getCustomName() !== $customName){
$xpCost++;
$resultItem->setCustomName($customName);
}

$result = new AnvilCraftResult($xpCost, $resultItem, $result->getSacrificeResult());
}

if($result === null || $result->getXpCost() <= 0 || ($result->getXpCost() > self::COST_LIMIT && !$isCreative)){
return null;
}

return $result;
}
}
66 changes: 66 additions & 0 deletions src/crafting/AnvilCraftResult.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?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;

/**
* This class is here to hold the result of an anvil crafting process.
*/
final class AnvilCraftResult{
/**
* @param Item|null $sacrificeResult If the given item is considered as null (count <= 0), the value will be set to null.
*/
public function __construct(
private int $xpCost,
private Item $output,
private ?Item $sacrificeResult
){
if($this->sacrificeResult !== null && $this->sacrificeResult->isNull()){
$this->sacrificeResult = null;
}
}

/**
* Represent the amount of experience points required to craft the output item.
*/
public function getXpCost() : int{
return $this->xpCost;
}

/**
* Represent the item given as output of the crafting process.
*/
public function getOutput() : Item{
return $this->output;
}

/**
* This result has to be null if the sacrifice slot need to be emptied.
* If not null, it represent the item that will be left in the sacrifice slot after the crafting process.
*/
public function getSacrificeResult() : ?Item{
return $this->sacrificeResult;
}
}
78 changes: 78 additions & 0 deletions src/crafting/AnvilCraftingManagerDataFiller.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?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\Durable;
use pocketmine\item\ToolTier;
use pocketmine\item\VanillaArmorMaterials;
use pocketmine\item\VanillaItems;
use pocketmine\world\format\io\GlobalItemDataHandlers;

final class AnvilCraftingManagerDataFiller{
public static function fillData(CraftingManager $manager) : CraftingManager{
foreach([
[
VanillaItems::DIAMOND(),
[VanillaArmorMaterials::DIAMOND(), ToolTier::DIAMOND]
], [
VanillaItems::GOLD_INGOT(),
[VanillaArmorMaterials::GOLD(), ToolTier::GOLD]
], [
VanillaItems::IRON_INGOT(),
[VanillaArmorMaterials::IRON(), ToolTier::IRON]
], [
VanillaItems::NETHERITE_INGOT(),
[VanillaArmorMaterials::NETHERITE(), ToolTier::NETHERITE]
], [
VanillaItems::SCUTE(),
[VanillaArmorMaterials::TURTLE(), null]
], [
VanillaItems::LEATHER(),
[VanillaArmorMaterials::LEATHER(), null]
]
] as [$item, [$armorMaterial, $toolTier]]){
$manager->registerAnvilRecipe(new MaterialRepairRecipe(
new ArmorRecipeIngredient($armorMaterial),
new ExactRecipeIngredient($item)
));
if($toolTier !== null){
$manager->registerAnvilRecipe(new MaterialRepairRecipe(
new TieredToolRecipeIngredient($toolTier),
new ExactRecipeIngredient($item)
));
}
}

foreach(VanillaItems::getAll() as $item){
if($item instanceof Durable){
$itemId = GlobalItemDataHandlers::getSerializer()->serializeType($item)->getName();
$manager->registerAnvilRecipe(new ItemSelfCombineRecipe(
new MetaWildcardRecipeIngredient($itemId)
));
}
}

return $manager;
}
}
30 changes: 30 additions & 0 deletions src/crafting/AnvilRecipe.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?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 AnvilRecipe{
public function getResultFor(Item $input, Item $material) : ?AnvilCraftResult;
}
50 changes: 50 additions & 0 deletions src/crafting/ArmorRecipeIngredient.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?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\Armor;
use pocketmine\item\ArmorMaterial;
use pocketmine\item\Item;
use function spl_object_id;

class ArmorRecipeIngredient implements RecipeIngredient{
public function __construct(
private ArmorMaterial $material
){
}

public function getMaterial() : ArmorMaterial{ return $this->material; }

public function accepts(Item $item) : bool{
if($item->getCount() < 1){
return false;
}

return $item instanceof Armor && $item->getMaterial() === $this->material;
}

public function __toString() : string{
return "ArmorRecipeIngredient(ArmorMaterial@" . spl_object_id($this->material) . ")";
}
}
45 changes: 45 additions & 0 deletions src/crafting/CraftingManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,18 @@ class CraftingManager{
*/
private array $brewingRecipeCache = [];

/**
* @var AnvilRecipe[]
* @phpstan-var list<AnvilRecipe>
*/
private array $anvilRecipes = [];

/**
* @var AnvilRecipe[][]
* @phpstan-var array<int, array<int, AnvilRecipe>>
*/
private array $anvilRecipeCache = [];

/** @phpstan-var ObjectSet<\Closure() : void> */
private ObjectSet $recipeRegisteredCallbacks;

Expand Down Expand Up @@ -187,6 +199,14 @@ public function getPotionContainerChangeRecipes() : array{
return $this->potionContainerChangeRecipes;
}

/**
* @return AnvilRecipe[][]
* @phpstan-return list<AnvilRecipe>
*/
public function getAnvilRecipes() : array{
return $this->anvilRecipes;
}

public function registerShapedRecipe(ShapedRecipe $recipe) : void{
$this->shapedRecipes[self::hashOutputs($recipe->getResults())][] = $recipe;
$this->craftingRecipeIndex[] = $recipe;
Expand Down Expand Up @@ -221,6 +241,14 @@ public function registerPotionContainerChangeRecipe(PotionContainerChangeRecipe
}
}

public function registerAnvilRecipe(AnvilRecipe $recipe) : void{
$this->anvilRecipes[] = $recipe;

foreach($this->recipeRegisteredCallbacks as $callback){
$callback();
}
}

/**
* @param Item[] $outputs
*/
Expand Down Expand Up @@ -294,4 +322,21 @@ public function matchBrewingRecipe(Item $input, Item $ingredient) : ?BrewingReci

return null;
}

public function matchAnvilRecipe(Item $input, Item $material) : ?AnvilRecipe{
$inputHash = $input->getStateId();
$materialHash = $material->getStateId();
$cached = $this->anvilRecipeCache[$inputHash][$materialHash] ?? null;
if($cached !== null){
return $cached;
}

foreach($this->anvilRecipes as $recipe){
if($recipe->getResultFor($input, $material) !== null){
return $this->anvilRecipeCache[$inputHash][$materialHash] = $recipe;
}
}

return null;
}
}
Loading
Loading