98 public static function matchIngredients(array $providedItems, array $recipeIngredients,
int $expectedIterations) : void{
99 if(count($recipeIngredients) === 0){
102 if(count($providedItems) === 0){
106 $packedProvidedItems = self::packItems(Utils::cloneObjectArray($providedItems));
107 $packedProvidedItemMatches = array_fill_keys(array_keys($packedProvidedItems), 0);
109 $recipeIngredientMatches = [];
111 foreach($recipeIngredients as $ingredientIndex => $recipeIngredient){
113 foreach($packedProvidedItems as $itemIndex => $packedItem){
114 if($recipeIngredient->accepts($packedItem)){
115 $packedProvidedItemMatches[$itemIndex]++;
116 $acceptedItems[$itemIndex] = $itemIndex;
120 if(count($acceptedItems) === 0){
121 throw new TransactionValidationException(
"No provided items satisfy ingredient requirement $recipeIngredient");
124 $recipeIngredientMatches[$ingredientIndex] = $acceptedItems;
127 foreach($packedProvidedItemMatches as $itemIndex => $itemMatchCount){
128 if($itemMatchCount === 0){
129 $item = $packedProvidedItems[$itemIndex];
130 throw new TransactionValidationException(
"Provided item $item is not accepted by any recipe ingredient");
137 uasort($recipeIngredientMatches, fn(array $a, array $b) => count($a) <=> count($b));
139 foreach($recipeIngredientMatches as $ingredientIndex => $acceptedItems){
140 $needed = $expectedIterations;
142 foreach($packedProvidedItems as $itemIndex => $item){
143 if(!isset($acceptedItems[$itemIndex])){
147 $taken = min($needed, $item->getCount());
149 $item->setCount($item->getCount() - $taken);
151 if($item->getCount() === 0){
152 unset($packedProvidedItems[$itemIndex]);
161 $recipeIngredient = $recipeIngredients[$ingredientIndex];
162 $actualIterations = $expectedIterations - $needed;
163 throw new TransactionValidationException(
"Not enough items to satisfy recipe ingredient $recipeIngredient for $expectedIterations (only have enough items for $actualIterations iterations)");
166 if(count($packedProvidedItems) > 0){
167 throw new TransactionValidationException(
"Not all provided items were used");
177 protected function matchOutputs(array $txItems, array $recipeItems) : int{
178 if(count($recipeItems) === 0){
181 if(count($txItems) === 0){
186 while(count($recipeItems) > 0){
188 $recipeItem = array_pop($recipeItems);
189 $needCount = $recipeItem->getCount();
190 foreach($recipeItems as $i => $otherRecipeItem){
191 if($otherRecipeItem->canStackWith($recipeItem)){
192 $needCount += $otherRecipeItem->getCount();
193 unset($recipeItems[$i]);
198 foreach($txItems as $j => $txItem){
199 if($txItem->canStackWith($recipeItem)){
200 $haveCount += $txItem->getCount();
205 if($haveCount % $needCount !== 0){
207 throw new TransactionValidationException(
"Expected an exact multiple of required $recipeItem (given: $haveCount, needed: $needCount)");
210 $multiplier = intdiv($haveCount, $needCount);
212 throw new TransactionValidationException(
"Expected more than zero items matching $recipeItem (given: $haveCount, needed: $needCount)");
214 if($iterations === 0){
215 $iterations = $multiplier;
216 }elseif($multiplier !== $iterations){
218 throw new TransactionValidationException(
"Expected $recipeItem x$iterations, but found x$multiplier");
222 if(count($txItems) > 0){
224 throw new TransactionValidationException(
"Expected 0 items left over, have " . count($txItems));
230 private function validateRecipe(CraftingRecipe $recipe, ?
int $expectedRepetitions) : int{
243 $this->squashDuplicateSlotChanges();
244 if(count($this->actions) < 1){
248 $this->matchItems($this->outputs, $this->inputs);
250 if($this->recipe ===
null){
252 foreach($this->craftingManager->matchRecipeByOutputs($this->outputs) as $recipe){
255 $this->repetitions = $this->matchOutputs($this->outputs, $recipe->getResultsFor($this->source->getCraftingGrid()));
257 self::matchIngredients($this->inputs, $recipe->getIngredientList(), $this->repetitions);
260 $this->recipe = $recipe;
268 if($this->recipe ===
null){
269 throw new TransactionValidationException(
"Unable to match a recipe to transaction (tried to match against $failed recipes)");
272 $this->repetitions = $this->validateRecipe($this->recipe, $this->repetitions);
277 $ev = new CraftItemEvent($this, $this->recipe, $this->repetitions, $this->inputs, $this->outputs);