Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
94 changes: 94 additions & 0 deletions src/crafting/CraftingManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
use pocketmine\utils\DestructorCallbackTrait;
use pocketmine\utils\ObjectSet;
use function array_shift;
use function array_search;
use function count;
use function implode;
use function ksort;
Expand Down Expand Up @@ -84,25 +85,39 @@ class CraftingManager{
/** @phpstan-var ObjectSet<\Closure() : void> */
private ObjectSet $recipeRegisteredCallbacks;

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

public function __construct(){
$this->recipeRegisteredCallbacks = new ObjectSet();
$this->recipeUnregisteredCallbacks = new ObjectSet();

foreach(FurnaceType::cases() as $furnaceType){
$this->furnaceRecipeManagers[spl_object_id($furnaceType)] = new FurnaceRecipeManager();
}

$recipeRegisteredCallbacks = $this->recipeRegisteredCallbacks;
$recipeUnregisteredCallbacks = $this->recipeUnregisteredCallbacks;
foreach($this->furnaceRecipeManagers as $furnaceRecipeManager){
$furnaceRecipeManager->getRecipeRegisteredCallbacks()->add(static function(FurnaceRecipe $recipe) use ($recipeRegisteredCallbacks) : void{
foreach($recipeRegisteredCallbacks as $callback){
$callback();
}
});
$furnaceRecipeManager->getRecipeUnregisteredCallbacks()->add(static function(FurnaceRecipe $recipe) use ($recipeUnregisteredCallbacks) : void{
foreach($recipeUnregisteredCallbacks as $callback){
$callback();
}
});
}
}

/** @phpstan-return ObjectSet<\Closure() : void> */
public function getRecipeRegisteredCallbacks() : ObjectSet{ return $this->recipeRegisteredCallbacks; }

/** @phpstan-return ObjectSet<\Closure() : void> */
public function getRecipeUnregisteredCallbacks() : ObjectSet{ return $this->recipeUnregisteredCallbacks; }

private static function hashOutput(Item $output) : string{
$write = new ByteBufferWriter();
VarInt::writeSignedInt($write, $output->getStateId());
Expand Down Expand Up @@ -188,6 +203,34 @@ public function registerShapedRecipe(ShapedRecipe $recipe) : void{
}
}

public function unregisterShapedRecipe(ShapedRecipe $recipe) : void{
$changed = false;
$hash = self::hashOutputs($recipe->getResults());

foreach($this->shapedRecipes[$hash] ?? [] as $i => $r){
if($r === $recipe){
array_splice($this->shapedRecipes[$hash], $i, 1);
if(count($this->shapedRecipes[$hash]) === 0){
unset($this->shapedRecipes[$hash]);
$changed = true;
}
break;
}
}

$index = array_search($recipe, $this->craftingRecipeIndex, true);
if($index !== false){
unset($this->craftingRecipeIndex[$index]);
$changed = true;
}

if($changed){
foreach($this->recipeUnregisteredCallbacks as $callback){
$callback();
}
}
}

public function registerShapelessRecipe(ShapelessRecipe $recipe) : void{
$this->shapelessRecipes[self::hashOutputs($recipe->getResults())][] = $recipe;
$this->craftingRecipeIndex[] = $recipe;
Expand All @@ -197,6 +240,35 @@ public function registerShapelessRecipe(ShapelessRecipe $recipe) : void{
}
}

public function unregisterShapelessRecipe(ShapelessRecipe $recipe) : void{
$changed = false;
$hash = self::hashOutputs($recipe->getResults());

foreach($this->shapelessRecipes[$hash] ?? [] as $i => $r){
if($r->isEquivalent($recipe)){
array_splice($this->shapelessRecipes[$hash], $i, 1);
if(count($this->shapelessRecipes[$hash]) === 0){
unset($this->shapelessRecipes[$hash]);
$changed = true;
}
// We don't break as it can have many similar recipes ?
}
}

foreach($this->craftingRecipeIndex as $index => $testRecipe){
if($testRecipe instanceof ShapelessRecipe && $recipe->isEquivalent($testRecipe)){
unset($this->craftingRecipeIndex[$index]);
$changed = true;
}
}

if($changed){
foreach($this->recipeUnregisteredCallbacks as $callback){
$callback();
}
}
}

public function registerPotionTypeRecipe(PotionTypeRecipe $recipe) : void{
$this->potionTypeRecipes[] = $recipe;

Expand All @@ -205,6 +277,17 @@ public function registerPotionTypeRecipe(PotionTypeRecipe $recipe) : void{
}
}

public function unregisterPotionTypeRecipe(PotionTypeRecipe $recipe) : void{
$recipeIndex = array_search($recipe, $this->potionTypeRecipes, true);
if($recipeIndex !== false){
array_splice($this->potionTypeRecipes, $recipeIndex, 1);

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

public function registerPotionContainerChangeRecipe(PotionContainerChangeRecipe $recipe) : void{
$this->potionContainerChangeRecipes[] = $recipe;

Expand All @@ -213,6 +296,17 @@ public function registerPotionContainerChangeRecipe(PotionContainerChangeRecipe
}
}

public function unregisterPotionContainerChangeRecipe(PotionContainerChangeRecipe $recipe) : void{
$recipeIndex = array_search($recipe, $this->potionContainerChangeRecipes, true);
if($recipeIndex !== false){
array_splice($this->potionContainerChangeRecipes, $recipeIndex, 1);

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

/**
* @param Item[] $outputs
*/
Expand Down
4 changes: 4 additions & 0 deletions src/crafting/ExactRecipeIngredient.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,8 @@ public function accepts(Item $item) : bool{
public function __toString() : string{
return "ExactRecipeIngredient(" . $this->item . ")";
}

public function isEquivalent(RecipeIngredient $other) : bool{
return $other instanceof ExactRecipeIngredient && $this->item->equals($other->item, true, $this->item->hasNamedTag());
}
}
23 changes: 23 additions & 0 deletions src/crafting/FurnaceRecipeManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

use pocketmine\item\Item;
use pocketmine\utils\ObjectSet;
use function array_search;

final class FurnaceRecipeManager{
/** @var FurnaceRecipe[] */
Expand All @@ -39,8 +40,12 @@ final class FurnaceRecipeManager{
/** @phpstan-var ObjectSet<\Closure(FurnaceRecipe) : void> */
private ObjectSet $recipeRegisteredCallbacks;

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

public function __construct(){
$this->recipeRegisteredCallbacks = new ObjectSet();
$this->recipeUnregisteredCallbacks = new ObjectSet();
}

/**
Expand All @@ -50,6 +55,13 @@ public function getRecipeRegisteredCallbacks() : ObjectSet{
return $this->recipeRegisteredCallbacks;
}

/**
* @phpstan-return ObjectSet<\Closure(FurnaceRecipe) : void>
*/
public function getRecipeUnregisteredCallbacks() : ObjectSet{
return $this->recipeUnregisteredCallbacks;
}

/**
* @return FurnaceRecipe[]
*/
Expand All @@ -64,6 +76,17 @@ public function register(FurnaceRecipe $recipe) : void{
}
}

public function unregister(FurnaceRecipe $recipe) : void{
$index = array_search($recipe, $this->furnaceRecipes, true);
if($index !== false){
unset($this->furnaceRecipes[$index]);

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

public function match(Item $input) : ?FurnaceRecipe{
$index = $input->getStateId();
$simpleRecipe = $this->lookupCache[$index] ?? null;
Expand Down
4 changes: 4 additions & 0 deletions src/crafting/MetaWildcardRecipeIngredient.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,8 @@ public function accepts(Item $item) : bool{
public function __toString() : string{
return "MetaWildcardRecipeIngredient($this->itemId)";
}

public function isEquivalent(RecipeIngredient $other) : bool{
return $other instanceof MetaWildcardRecipeIngredient && $other->itemId === $this->itemId;
}
}
5 changes: 5 additions & 0 deletions src/crafting/RecipeIngredient.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,9 @@
interface RecipeIngredient extends \Stringable{

public function accepts(Item $item) : bool;

/**
* Check if the given ingredient is equivalent to this one.
*/
public function isEquivalent(RecipeIngredient $other) : bool;
}
25 changes: 25 additions & 0 deletions src/crafting/ShapelessRecipe.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,29 @@ public function matchesCraftingGrid(CraftingGrid $grid) : bool{

return count($input) === 0; //crafting grid should be empty apart from the given ingredient stacks
}

public function isEquivalent(ShapelessRecipe $recipe) : bool{
if($this->getType() !== $recipe->getType()){
return false;
}

if(!Utils::areUnorderedArraysEqual(
$this->getResults(),
$recipe->getResults(),
static fn(Item $a, Item $b) : bool => $a->equalsExact($b)
)){
return false;
}


if(!Utils::areUnorderedArraysEqual(
$this->getIngredientList(),
$recipe->getIngredientList(),
static fn(RecipeIngredient $a, RecipeIngredient $b) : bool => $a->isEquivalent($b)
)){
return false;
}

return true;
}
}
4 changes: 4 additions & 0 deletions src/crafting/TagWildcardRecipeIngredient.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,8 @@ public function accepts(Item $item) : bool{
public function __toString() : string{
return "TagWildcardRecipeIngredient($this->tagName)";
}

public function isEquivalent(RecipeIngredient $other) : bool{
return $other instanceof TagWildcardRecipeIngredient && $other->tagName === $this->tagName;
}
}
3 changes: 3 additions & 0 deletions src/network/mcpe/cache/CraftingDataCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ public function getCache(CraftingManager $manager) : CraftingDataPacket{
$manager->getRecipeRegisteredCallbacks()->add(function() use ($id) : void{
unset($this->caches[$id]);
});
$manager->getRecipeUnregisteredCallbacks()->add(function() use ($id) : void{
unset($this->caches[$id]);
});
$this->caches[$id] = $this->buildCraftingDataCache($manager);
}
return $this->caches[$id];
Expand Down
39 changes: 39 additions & 0 deletions src/utils/Utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -685,4 +685,43 @@
public static function getRandomFloat() : float{
return mt_rand() / mt_getrandmax();
}

/*
* Compares two arrays to determine whether they contain equivalent elements,
* regardless of their order.
*
* Two elements are considered equal if the provided `$check` callback returns `true`.
* Each element in `$tab1` must match exactly one unique element in `$tab2`, and vice versa.
* Duplicates are taken into account: two identical elements must each have a match in the other array.
*
* @template T
*
* @phpstan-param T[] $tab1 The first array to compare.
* @phpstan-param T[] $tab2 The second array to compare.
* @phpstan-param \Closure(T, T): bool $check A custom equality function to compare two elements.
*/
public static function areUnorderedArraysEqual(array $tab1, array $tab2, \Closure $check) : bool {

Check failure on line 703 in src/utils/Utils.php

View workflow job for this annotation

GitHub Actions / PHP 8.3 / PHPStan analysis

Method pocketmine\utils\Utils::areUnorderedArraysEqual() has parameter $tab2 with no value type specified in iterable type array.

Check failure on line 703 in src/utils/Utils.php

View workflow job for this annotation

GitHub Actions / PHP 8.3 / PHPStan analysis

Method pocketmine\utils\Utils::areUnorderedArraysEqual() has parameter $tab1 with no value type specified in iterable type array.

Check failure on line 703 in src/utils/Utils.php

View workflow job for this annotation

GitHub Actions / PHP 8.3 / PHPStan analysis

Method pocketmine\utils\Utils::areUnorderedArraysEqual() has parameter $check with no signature specified for Closure.

Check failure on line 703 in src/utils/Utils.php

View workflow job for this annotation

GitHub Actions / PHP 8.4 / PHPStan analysis

Method pocketmine\utils\Utils::areUnorderedArraysEqual() has parameter $tab2 with no value type specified in iterable type array.

Check failure on line 703 in src/utils/Utils.php

View workflow job for this annotation

GitHub Actions / PHP 8.4 / PHPStan analysis

Method pocketmine\utils\Utils::areUnorderedArraysEqual() has parameter $tab1 with no value type specified in iterable type array.

Check failure on line 703 in src/utils/Utils.php

View workflow job for this annotation

GitHub Actions / PHP 8.4 / PHPStan analysis

Method pocketmine\utils\Utils::areUnorderedArraysEqual() has parameter $check with no signature specified for Closure.
if(count($tab1) !== count($tab2)){
return false;
}

// Check that the two lists of results are identical, regardless of the order.
$used = [];
foreach($tab1 as $element1){
$found = false;
foreach($tab2 as $i => $element2){

Check failure on line 712 in src/utils/Utils.php

View workflow job for this annotation

GitHub Actions / PHP 8.3 / PHPStan analysis

Possible unreported errors in foreach on array with unspecified key type.

Check failure on line 712 in src/utils/Utils.php

View workflow job for this annotation

GitHub Actions / PHP 8.4 / PHPStan analysis

Possible unreported errors in foreach on array with unspecified key type.
if(!isset($used[$i]) && $check($element1, $element2)){
$used[$i] = $element2;
$found = true;
break;
}
}

if(!$found){
return false;
}
}

return true;
}
}
Loading
Loading