Crafting: nuke

This commit brings in a much-needed rewrite of crafting transaction handling.

The following classes have been removed:
- CraftingTransferMaterialAction
- CraftingTakeResultAction

The following classes have significant changes:
- CraftingTransaction
	- All API methods have been removed and are now handled in CraftItemEvent
- CraftItemEvent
	- added the following:
		- getInputs()
		- getOutputs()
		- getRepetitions() (tells how many times a recipe was crafted in this event)
- Recipe interface:
	- Removed getResult() (individual recipes may handle this differently)
- CraftingRecipe interface
	- removed the following:
		- matchItems()
		- getExtraResults()
		- getAllResults()
	- added the following
		- getResults()
		- getIngredientList() : Item[], which must return a 1D array of items that should be consumed (wildcards accepted).
		- matchesCraftingGrid(CraftingGrid)
- ShapedRecipe
	- constructor now accepts string[], Item[], Item[]
- ShapelessRecipe
	- constructor now accepts Item[], Item[]
This commit is contained in:
Dylan K. Taylor
2018-03-26 13:23:28 +01:00
parent bc836aaec1
commit 8572e9e560
14 changed files with 437 additions and 521 deletions

View File

@ -28,11 +28,6 @@ use pocketmine\item\ItemFactory;
use pocketmine\utils\UUID;
class ShapedRecipe implements CraftingRecipe{
/** @var Item */
private $primaryResult;
/** @var Item[] */
private $extraResults = [];
/** @var UUID|null */
private $id = null;
@ -40,90 +35,76 @@ class ShapedRecipe implements CraftingRecipe{
private $shape = [];
/** @var Item[] char => Item map */
private $ingredientList = [];
/** @var Item[] */
private $results = [];
/** @var int */
private $height;
/** @var int */
private $width;
/**
* Constructs a ShapedRecipe instance.
*
* @param Item $primaryResult
* @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 Item[] $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[] $extraResults<br>
* List of additional result items to leave in the crafting grid afterwards. Used for things like cake recipe
* empty buckets.
* @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.
*/
public function __construct(Item $primaryResult, array $shape, array $ingredients, array $extraResults = []){
$rowCount = count($shape);
if($rowCount > 3 or $rowCount <= 0){
throw new \InvalidArgumentException("Shaped recipes may only have 1, 2 or 3 rows, not $rowCount");
public function __construct(array $shape, array $ingredients, array $results){
$this->height = count($shape);
if($this->height > 3 or $this->height <= 0){
throw new \InvalidArgumentException("Shaped recipes may only have 1, 2 or 3 rows, not $this->height");
}
$shape = array_values($shape);
$columnCount = strlen($shape[0]);
if($columnCount > 3 or $columnCount <= 0){
throw new \InvalidArgumentException("Shaped recipes may only have 1, 2 or 3 columns, not $columnCount");
$this->width = strlen($shape[0]);
if($this->width > 3 or $this->width <= 0){
throw new \InvalidArgumentException("Shaped recipes may only have 1, 2 or 3 columns, not $this->width");
}
foreach($shape as $y => $row){
if(strlen($row) !== $columnCount){
throw new \InvalidArgumentException("Shaped recipe rows must all have the same length (expected $columnCount, got " . strlen($row) . ")");
if(strlen($row) !== $this->width){
throw new \InvalidArgumentException("Shaped recipe rows must all have the same length (expected $this->width, got " . strlen($row) . ")");
}
for($x = 0; $x < $columnCount; ++$x){
for($x = 0; $x < $this->width; ++$x){
if($row{$x} !== ' ' and !isset($ingredients[$row{$x}])){
throw new \InvalidArgumentException("No item specified for symbol '" . $row{$x} . "'");
}
}
}
$this->primaryResult = clone $primaryResult;
foreach($extraResults as $item){
$this->extraResults[] = clone $item;
}
$this->shape = $shape;
foreach($ingredients as $char => $i){
$this->setIngredient($char, $i);
}
$this->results = array_map(function(Item $item) : Item{ return clone $item; }, $results);
}
public function getWidth() : int{
return strlen($this->shape[0]);
return $this->width;
}
public function getHeight() : int{
return count($this->shape);
}
/**
* @return Item
*/
public function getResult() : Item{
return $this->primaryResult;
return $this->height;
}
/**
* @return Item[]
*/
public function getExtraResults() : array{
return $this->extraResults;
}
/**
* @return Item[]
*/
public function getAllResults() : array{
$results = $this->extraResults;
array_unshift($results, $this->primaryResult);
return $results;
public function getResults() : array{
return array_map(function(Item $item) : Item{ return clone $item; }, $this->results);
}
/**
@ -164,8 +145,8 @@ class ShapedRecipe implements CraftingRecipe{
public function getIngredientMap() : array{
$ingredients = [];
for($y = 0, $y2 = $this->getHeight(); $y < $y2; ++$y){
for($x = 0, $x2 = $this->getWidth(); $x < $x2; ++$x){
for($y = 0; $y < $this->height; ++$y){
for($x = 0; $x < $this->width; ++$x){
$ingredients[$y][$x] = $this->getIngredient($x, $y);
}
}
@ -173,6 +154,24 @@ class ShapedRecipe implements CraftingRecipe{
return $ingredients;
}
/**
* @return Item[]
*/
public function getIngredientList() : array{
$ingredients = [];
for($y = 0; $y < $this->height; ++$y){
for($x = 0; $x < $this->width; ++$x){
$ingredient = $this->getIngredient($x, $y);
if(!$ingredient->isNull()){
$ingredients[] = $ingredient;
}
}
}
return $ingredients;
}
/**
* @param int $x
* @param int $y
@ -197,34 +196,20 @@ class ShapedRecipe implements CraftingRecipe{
}
/**
* @param Item[][] $input
* @param CraftingGrid $grid
* @param bool $reverse
*
* @return bool
*/
private function matchInputMap(array $input) : bool{
$map = $this->getIngredientMap();
private function matchInputMap(CraftingGrid $grid, bool $reverse) : bool{
for($y = 0; $y < $this->height; ++$y){
for($x = 0; $x < $this->width; ++$x){
//match the given items to the requested items
for($y = 0, $y2 = $this->getHeight(); $y < $y2; ++$y){
for($x = 0, $x2 = $this->getWidth(); $x < $x2; ++$x){
$given = $input[$y][$x] ?? null;
$required = $map[$y][$x];
if($given === null or !$required->equals($given, !$required->hasAnyDamageValue(), $required->hasCompoundTag()) or $required->getCount() !== $given->getCount()){
$given = $grid->getIngredient($reverse ? $this->width - $x - 1 : $x, $y);
$required = $this->getIngredient($x, $y);
if(!$required->equals($given, !$required->hasAnyDamageValue(), $required->hasCompoundTag()) or $required->getCount() > $given->getCount()){
return false;
}
unset($input[$y][$x]);
}
}
//check if there are any items left in the grid outside of the recipe
/** @var Item[] $row */
foreach($input as $y => $row){
foreach($row as $x => $needItem){
if(!$needItem->isNull()){
return false; //too many input ingredients
}
}
}
@ -232,38 +217,15 @@ class ShapedRecipe implements CraftingRecipe{
}
/**
* @param Item[][] $input
* @param Item[][] $output
* @param CraftingGrid $grid
*
* @return bool
*/
public function matchItems(array $input, array $output) : bool{
if(
!$this->matchInputMap($input) and //as-is
!$this->matchInputMap(array_map(function(array $row) : array{ return array_reverse($row, false); }, $input)) //mirrored
){
public function matchesCraftingGrid(CraftingGrid $grid) : bool{
if($this->width !== $grid->getRecipeWidth() or $this->height !== $grid->getRecipeHeight()){
return false;
}
//and then, finally, check that the output items are good:
/** @var Item[] $haveItems */
$haveItems = array_merge(...$output);
$needItems = $this->getExtraResults();
foreach($haveItems as $j => $haveItem){
if($haveItem->isNull()){
unset($haveItems[$j]);
continue;
}
foreach($needItems as $i => $needItem){
if($needItem->equals($haveItem, !$needItem->hasAnyDamageValue(), $needItem->hasCompoundTag()) and $needItem->getCount() === $haveItem->getCount()){
unset($haveItems[$j], $needItems[$i]);
break;
}
}
}
return count($haveItems) === 0 and count($needItems) === 0;
return $this->matchInputMap($grid, false) or $this->matchInputMap($grid, true);
}
}