• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

CyclopsMC / IntegratedCrafting / #479011850

09 Mar 2026 07:58AM UTC coverage: 24.357% (+0.06%) from 24.3%
#479011850

push

github

rubensworks
Bump mod version

758 of 3112 relevant lines covered (24.36%)

0.24 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

72.77
/src/main/java/org/cyclops/integratedcrafting/core/CraftingHelpers.java
1
package org.cyclops.integratedcrafting.core;
2

3
import com.google.common.collect.Iterables;
4
import com.google.common.collect.Lists;
5
import com.google.common.collect.Maps;
6
import com.google.common.collect.Sets;
7
import net.minecraft.core.Direction;
8
import net.minecraft.world.level.block.entity.BlockEntity;
9
import net.minecraftforge.common.capabilities.ICapabilityProvider;
10
import net.minecraftforge.common.util.LazyOptional;
11
import org.apache.commons.lang3.tuple.Pair;
12
import org.apache.logging.log4j.Level;
13
import org.cyclops.commoncapabilities.api.capability.recipehandler.IPrototypedIngredientAlternatives;
14
import org.cyclops.commoncapabilities.api.capability.recipehandler.IRecipeDefinition;
15
import org.cyclops.commoncapabilities.api.ingredient.*;
16
import org.cyclops.commoncapabilities.api.ingredient.storage.IIngredientComponentStorage;
17
import org.cyclops.commoncapabilities.api.ingredient.storage.IngredientComponentStorageEmpty;
18
import org.cyclops.cyclopscore.helper.BlockEntityHelpers;
19
import org.cyclops.cyclopscore.ingredient.collection.*;
20
import org.cyclops.cyclopscore.ingredient.storage.IngredientComponentStorageSlottedCollectionWrapper;
21
import org.cyclops.integratedcrafting.Capabilities;
22
import org.cyclops.integratedcrafting.IntegratedCrafting;
23
import org.cyclops.integratedcrafting.api.crafting.*;
24
import org.cyclops.integratedcrafting.api.network.ICraftingNetwork;
25
import org.cyclops.integratedcrafting.api.recipe.IRecipeIndex;
26
import org.cyclops.integratedcrafting.capability.network.CraftingNetworkConfig;
27
import org.cyclops.integrateddynamics.IntegratedDynamics;
28
import org.cyclops.integrateddynamics.api.PartStateException;
29
import org.cyclops.integrateddynamics.api.ingredient.capability.IPositionedAddonsNetworkIngredientsHandler;
30
import org.cyclops.integrateddynamics.api.network.INetwork;
31
import org.cyclops.integrateddynamics.api.network.IPositionedAddonsNetworkIngredients;
32
import org.cyclops.integrateddynamics.api.part.PartPos;
33
import org.cyclops.integrateddynamics.core.helper.NetworkHelpers;
34
import org.cyclops.integrateddynamics.core.network.IngredientChannelAdapter;
35
import org.cyclops.integrateddynamics.core.network.IngredientChannelIndexed;
36

37
import javax.annotation.Nullable;
38
import java.util.*;
39
import java.util.function.Function;
40
import java.util.stream.Collectors;
41
import java.util.stream.IntStream;
42

43
/**
44
 * Helpers related to handling crafting jobs.
45
 * @author rubensworks
46
 */
47
public class CraftingHelpers {
×
48

49
    /**
50
     * Get the network at the given position,
51
     * or throw a PartStateException if it is null.
52
     * @param pos A position.
53
     * @return A network.
54
     * @throws PartStateException If the network could not be found.
55
     */
56
    public static INetwork getNetworkChecked(PartPos pos) throws PartStateException {
57
        INetwork network = NetworkHelpers.getNetwork(pos.getPos().getLevel(true), pos.getPos().getBlockPos(), pos.getSide()).orElse(null);
×
58
        if (network == null) {
×
59
            IntegratedDynamics.clog(Level.ERROR, "Could not get the network for transfer as no network was found.");
×
60
            throw new PartStateException(pos.getPos(), pos.getSide());
×
61
        }
62
        return network;
×
63
    }
64

65
    /**
66
     * Get the crafting network in the given network.
67
     * @param network A network.
68
     * @return The crafting network.
69
     */
70
    public static LazyOptional<ICraftingNetwork> getCraftingNetwork(@Nullable INetwork network) {
71
        if (network != null) {
×
72
            return network.getCapability(CraftingNetworkConfig.CAPABILITY);
×
73
        }
74
        return null;
×
75
    }
76

77
    /**
78
     * Get the crafting network in the given network.
79
     * @param network A network.
80
     * @return The crafting network.
81
     */
82
    public static ICraftingNetwork getCraftingNetworkChecked(@Nullable INetwork network) {
83
        return getCraftingNetwork(network)
×
84
                .orElseThrow(() -> new IllegalStateException("Could not find a crafting network"));
×
85
    }
86

87
    /**
88
     * Get the storage network of the given type in the given network.
89
     * @param network A network.
90
     * @param ingredientComponent The ingredient component type of the network.
91
     * @param <T> The instance type.
92
     * @param <M> The matching condition parameter.
93
     * @return The storage network.
94
     */
95
    public static <T, M> LazyOptional<IPositionedAddonsNetworkIngredients<T, M>> getIngredientsNetwork(INetwork network,
96
                                                                                                       IngredientComponent<T, M> ingredientComponent) {
97
        IPositionedAddonsNetworkIngredientsHandler<T, M> ingredientsHandler = ingredientComponent.getCapability(Capabilities.POSITIONED_ADDONS_NETWORK_INGREDIENTS_HANDLER).orElse(null);
×
98
        if (ingredientsHandler != null) {
×
99
            return ingredientsHandler.getStorage(network);
×
100
        }
101
        return LazyOptional.empty();
×
102
    }
103

104
    /**
105
     * Get the storage network of the given type in the given network.
106
     * @param network A network.
107
     * @param ingredientComponent The ingredient component type of the network.
108
     * @param <T> The instance type.
109
     * @param <M> The matching condition parameter.
110
     * @return The storage network.
111
     */
112
    public static <T, M> IPositionedAddonsNetworkIngredients<T, M> getIngredientsNetworkChecked(INetwork network,
113
                                                                                                IngredientComponent<T, M> ingredientComponent) {
114
        return getIngredientsNetwork(network, ingredientComponent)
×
115
                .orElseThrow(() -> new IllegalStateException("Could not find an ingredients network"));
×
116
    }
117

118
    /**
119
     * Get the storage of the given ingredient component type from the network.
120
     * @param network The network.
121
     * @param channel A network channel.
122
     * @param ingredientComponent The ingredient component type of the network.
123
     * @param scheduleObservation If an observation inside the ingredients network should be scheduled.
124
     * @param <T> The instance type.
125
     * @param <M> The matching condition parameter.
126
     * @return The storage.
127
     */
128
    public static <T, M> IIngredientComponentStorage<T, M> getNetworkStorage(INetwork network, int channel,
129
                                                                             IngredientComponent<T, M> ingredientComponent,
130
                                                                             boolean scheduleObservation) {
131
        IPositionedAddonsNetworkIngredients<T, M> ingredientsNetwork = getIngredientsNetwork(network, ingredientComponent).orElse(null);
×
132
        if (ingredientsNetwork != null) {
×
133
            if (scheduleObservation) {
×
134
                ingredientsNetwork.scheduleObservation();
×
135
            }
136
            return ingredientsNetwork.getChannel(channel);
×
137
        }
138
        return new IngredientComponentStorageEmpty<>(ingredientComponent);
×
139
    }
140

141
    /**
142
     * Calculate the required crafting jobs and their dependencies for the given instance in the given network.
143
     * @param network The target network.
144
     * @param channel The target channel.
145
     * @param ingredientComponent The ingredient component type of the instance.
146
     * @param instance The instance to craft.
147
     * @param matchCondition The match condition of the instance.
148
     * @param craftMissing If the missing required ingredients should also be crafted.
149
     * @param identifierGenerator identifierGenerator An ID generator for crafting jobs.
150
     * @param craftingJobsGraph The target graph where all dependencies will be stored.
151
     * @param collectMissingRecipes If the missing recipes should be collected inside
152
     *                              {@link UnknownCraftingRecipeException}.
153
     *                              This may slow down calculation for deeply nested recipe graphs.
154
     * @param <T> The instance type.
155
     * @param <M> The matching condition parameter.
156
     * @return The crafting job for the given instance.
157
     * @throws UnknownCraftingRecipeException If the recipe for a (sub)ingredient is unavailable.
158
     * @throws RecursiveCraftingRecipeException If an infinite recursive recipe was detected.
159
     */
160
    public static <T, M> CraftingJob calculateCraftingJobs(INetwork network, int channel,
161
                                                           IngredientComponent<T, M> ingredientComponent,
162
                                                           T instance, M matchCondition, boolean craftMissing,
163
                                                           IIdentifierGenerator identifierGenerator,
164
                                                           CraftingJobDependencyGraph craftingJobsGraph,
165
                                                           boolean collectMissingRecipes)
166
            throws UnknownCraftingRecipeException, RecursiveCraftingRecipeException {
167
        ICraftingNetwork craftingNetwork = getCraftingNetworkChecked(network);
×
168
        IRecipeIndex recipeIndex = craftingNetwork.getRecipeIndex(channel);
×
169
        Function<IngredientComponent<?, ?>, IIngredientComponentStorage> storageGetter = getNetworkStorageGetter(network, channel, true);
×
170

171
        CraftingJob craftingJob = calculateCraftingJobs(recipeIndex, channel, storageGetter, ingredientComponent, instance, matchCondition,
×
172
                craftMissing, Maps.newIdentityHashMap(), Maps.newIdentityHashMap(), identifierGenerator, craftingJobsGraph, Sets.newHashSet(),
×
173
                collectMissingRecipes);
174
        craftingJobsGraph.addCraftingJobId(craftingJob);
×
175
        return craftingJob;
×
176
    }
177

178
    /**
179
     * Calculate the required crafting jobs and their dependencies for the given instance in the given network.
180
     * @param network The target network.
181
     * @param channel The target channel.
182
     * @param recipe The recipe to calculate a job for.
183
     * @param amount The amount of times the recipe should be crafted.
184
     * @param craftMissing If the missing required ingredients should also be crafted.
185
     * @param identifierGenerator identifierGenerator An ID generator for crafting jobs.
186
     * @param craftingJobsGraph The target graph where all dependencies will be stored.
187
     * @param collectMissingRecipes If the missing recipes should be collected inside
188
     *                              {@link FailedCraftingRecipeException}.
189
     *                              This may slow down calculation for deeply nested recipe graphs.
190
     * @return The crafting job for the given instance.
191
     * @throws FailedCraftingRecipeException If the recipe could not be crafted due to missing sub-dependencies.
192
     * @throws RecursiveCraftingRecipeException If an infinite recursive recipe was detected.
193
     */
194
    public static CraftingJob calculateCraftingJobs(INetwork network, int channel,
195
                                                    IRecipeDefinition recipe, int amount, boolean craftMissing,
196
                                                    IIdentifierGenerator identifierGenerator,
197
                                                    CraftingJobDependencyGraph craftingJobsGraph,
198
                                                    boolean collectMissingRecipes)
199
            throws FailedCraftingRecipeException, RecursiveCraftingRecipeException {
200
        ICraftingNetwork craftingNetwork = getCraftingNetworkChecked(network);
×
201
        IRecipeIndex recipeIndex = craftingNetwork.getRecipeIndex(channel);
×
202
        Function<IngredientComponent<?, ?>, IIngredientComponentStorage> storageGetter = getNetworkStorageGetter(network, channel, true);
×
203

204
        PartialCraftingJobCalculation result = calculateCraftingJobs(recipeIndex, channel, storageGetter, recipe, amount,
×
205
                craftMissing, Maps.newIdentityHashMap(), Maps.newIdentityHashMap(), identifierGenerator, craftingJobsGraph, Sets.newHashSet(),
×
206
                collectMissingRecipes);
207
        if (result.getCraftingJob() == null) {
×
208
            throw new FailedCraftingRecipeException(recipe, amount, result.getMissingDependencies(),
×
209
                    compressMixedIngredients(new MixedIngredients(result.getIngredientsStorage())), result.getPartialCraftingJobs());
×
210
        } else {
211
            craftingJobsGraph.addCraftingJobId(result.getCraftingJob());
×
212
            return result.getCraftingJob();
×
213
        }
214
    }
215

216
    /**
217
     * @return An identifier generator for crafting jobs.
218
     */
219
    public static IIdentifierGenerator getGlobalCraftingJobIdentifier() {
220
        return () -> IntegratedCrafting.globalCounters.getNext("craftingJob");
×
221
    }
222

223
    /**
224
     * Calculate the effective quantity for the given instance in the output of the given recipe.
225
     * @param recipe A recipe.
226
     * @param ingredientComponent The ingredient component.
227
     * @param instance An instance.
228
     * @param matchCondition A match condition.
229
     * @param <T> The instance type.
230
     * @param <M> The matching condition parameter.
231
     * @return The effective quantity.
232
     */
233
    public static <T, M> long getOutputQuantityForRecipe(IRecipeDefinition recipe,
234
                                                         IngredientComponent<T, M> ingredientComponent,
235
                                                         T instance, M matchCondition) {
236
        IIngredientMatcher<T, M> matcher = ingredientComponent.getMatcher();
1✔
237
        return recipe.getOutput().getInstances(ingredientComponent)
1✔
238
                .stream()
1✔
239
                .filter(i -> matcher.matches(i, instance, matchCondition))
1✔
240
                .mapToLong(matcher::getQuantity)
1✔
241
                .sum();
1✔
242
    }
243

244
    /**
245
     * Calculate a crafting job for the given instance.
246
     *
247
     * This method is merely an easily-stubbable implementation for the method above,
248
     * to simplify unit testing.
249
     *
250
     * @param recipeIndex The recipe index.
251
     * @param channel The target channel that will be stored in created crafting jobs.
252
     * @param storageGetter A callback function to get a storage for the given ingredient component.
253
     * @param ingredientComponent The ingredient component type of the instance.
254
     * @param instance The instance to craft.
255
     * @param matchCondition The match condition of the instance.
256
     * @param craftMissing If the missing required ingredients should also be crafted.
257
     * @param simulatedExtractionMemory This map remembers all extracted instances in simulation mode.
258
     *                                  This is to make sure that instances can not be extracted multiple times
259
     *                                  when simulating.
260
     * @param extractionMemoryReusable Like simulatedExtractionMemory, but it stores the reusable ingredients.
261
     * @param identifierGenerator An ID generator for crafting jobs.
262
     * @param craftingJobsGraph The target graph where all dependencies will be stored.
263
     * @param parentDependencies A set of parent recipe dependencies that are pending.
264
     *                           This is used to check for infinite recursion in recipes.
265
     * @param collectMissingRecipes If the missing recipes should be collected inside
266
     *                              {@link UnknownCraftingRecipeException}.
267
     *                              This may slow down calculation for deeply nested recipe graphs.
268
     * @param <T> The instance type.
269
     * @param <M> The matching condition parameter.
270
     * @return The crafting job for the given instance.
271
     * @throws UnknownCraftingRecipeException If the recipe for a (sub)ingredient is unavailable.
272
     * @throws RecursiveCraftingRecipeException If an infinite recursive recipe was detected.
273
     */
274
    protected static <T, M> CraftingJob calculateCraftingJobs(IRecipeIndex recipeIndex, int channel,
275
                                                              Function<IngredientComponent<?, ?>, IIngredientComponentStorage> storageGetter,
276
                                                              IngredientComponent<T, M> ingredientComponent,
277
                                                              T instance, M matchCondition, boolean craftMissing,
278
                                                              Map<IngredientComponent<?, ?>,
279
                                                                      IngredientCollectionPrototypeMap<?, ?>> simulatedExtractionMemory,
280
                                                              Map<IngredientComponent<?, ?>,
281
                                                                      IIngredientCollectionMutable<?, ?>> extractionMemoryReusable,
282
                                                              IIdentifierGenerator identifierGenerator,
283
                                                              CraftingJobDependencyGraph craftingJobsGraph,
284
                                                              Set<IPrototypedIngredient> parentDependencies,
285
                                                              boolean collectMissingRecipes)
286
            throws UnknownCraftingRecipeException, RecursiveCraftingRecipeException {
287
        IIngredientMatcher<T, M> matcher = ingredientComponent.getMatcher();
1✔
288
        // This matching condition makes it so that the recipe output does not have to match with the requested input by quantity.
289
        M quantifierlessCondition = matcher.withoutCondition(matchCondition,
1✔
290
                ingredientComponent.getPrimaryQuantifier().getMatchCondition());
1✔
291
        long instanceQuantity = matcher.getQuantity(instance);
1✔
292

293
        // Loop over all available recipes, and return the first valid one.
294
        Iterator<IRecipeDefinition> recipes = recipeIndex.getRecipes(ingredientComponent, instance, quantifierlessCondition);
1✔
295
        List<UnknownCraftingRecipeException> firstMissingDependencies = Lists.newArrayList();
1✔
296
        Map<IngredientComponent<?, ?>, List<?>> firstIngredientsStorage = Collections.emptyMap();
1✔
297
        List<CraftingJob> firstPartialCraftingJobs = Lists.newArrayList();
1✔
298
        RecursiveCraftingRecipeException firstRecursiveException = null;
1✔
299
        while (recipes.hasNext()) {
1✔
300
            IRecipeDefinition recipe = recipes.next();
1✔
301

302
            // Calculate the quantity for the given instance that the recipe outputs
303
            long recipeOutputQuantity = getOutputQuantityForRecipe(recipe, ingredientComponent, instance, quantifierlessCondition);
1✔
304
            // Based on the quantity of the recipe output, calculate the amount of required recipe jobs.
305
            int amount = (int) Math.ceil(((float) instanceQuantity) / (float) recipeOutputQuantity);
1✔
306

307
            // Calculate jobs for the given recipe
308
            try {
309
                PartialCraftingJobCalculation result = calculateCraftingJobs(recipeIndex, channel,
1✔
310
                        storageGetter, recipe, amount, craftMissing,
311
                        simulatedExtractionMemory, extractionMemoryReusable, identifierGenerator, craftingJobsGraph, parentDependencies,
312
                        collectMissingRecipes && firstMissingDependencies.isEmpty());
1✔
313
                if (result.getCraftingJob() == null) {
1✔
314
                    firstMissingDependencies = result.getMissingDependencies();
1✔
315
                    firstIngredientsStorage = result.getIngredientsStorage();
1✔
316
                    if (result.getPartialCraftingJobs() != null) {
1✔
317
                        firstPartialCraftingJobs = result.getPartialCraftingJobs();
1✔
318
                    }
319
                } else {
320
                    return result.getCraftingJob();
1✔
321
                }
322
            } catch (RecursiveCraftingRecipeException e) {
1✔
323
                if (firstRecursiveException == null) {
1✔
324
                    firstRecursiveException = e;
1✔
325
                }
326
                continue;
1✔
327
            }
1✔
328
        }
1✔
329

330
        if (firstRecursiveException != null) {
1✔
331
            throw firstRecursiveException;
1✔
332
        }
333

334
        // No valid recipes were available, so we error or collect the missing instance.
335
        throw new UnknownCraftingRecipeException(new PrototypedIngredient<>(ingredientComponent, instance, matchCondition),
1✔
336
                matcher.getQuantity(instance), firstMissingDependencies, compressMixedIngredients(new MixedIngredients(firstIngredientsStorage)),
1✔
337
                firstPartialCraftingJobs);
338
    }
339

340
    /**
341
     * Calculate a crafting job for the given recipe.
342
     *
343
     * This method is merely an easily-stubbable implementation for the method above,
344
     * to simplify unit testing.
345
     *
346
     * @param recipeIndex The recipe index.
347
     * @param channel The target channel that will be stored in created crafting jobs.
348
     * @param storageGetter A callback function to get a storage for the given ingredient component.
349
     * @param recipe The recipe to calculate a job for.
350
     * @param amount The amount of times the recipe should be crafted.
351
     * @param craftMissing If the missing required ingredients should also be crafted.
352
     * @param simulatedExtractionMemory This map remembers all extracted instances in simulation mode.
353
     *                                  This is to make sure that instances can not be extracted multiple times
354
     *                                  when simulating.
355
     * @param extractionMemoryReusable Like simulatedExtractionMemory, but it stores the reusable ingredients.
356
     * @param identifierGenerator An ID generator for crafting jobs.
357
     * @param craftingJobsGraph The target graph where all dependencies will be stored.
358
     * @param parentDependencies A set of parent recipe dependencies that are pending.
359
     *                           This is used to check for infinite recursion in recipes.
360
     * @param collectMissingRecipes If the missing recipes should be collected inside
361
     *                              {@link UnknownCraftingRecipeException}.
362
     *                              This may slow down calculation for deeply nested recipe graphs.
363
     * @return The crafting job for the given instance.
364
     * @throws RecursiveCraftingRecipeException If an infinite recursive recipe was detected.
365
     */
366
    protected static PartialCraftingJobCalculation calculateCraftingJobs(
367
            IRecipeIndex recipeIndex, int channel,
368
            Function<IngredientComponent<?, ?>, IIngredientComponentStorage> storageGetter,
369
            IRecipeDefinition recipe, int amount, boolean craftMissing,
370
            Map<IngredientComponent<?, ?>,
371
                    IngredientCollectionPrototypeMap<?, ?>> simulatedExtractionMemory,
372
            Map<IngredientComponent<?, ?>,
373
                    IIngredientCollectionMutable<?, ?>> extractionMemoryReusable,
374
            IIdentifierGenerator identifierGenerator,
375
            CraftingJobDependencyGraph craftingJobsGraph,
376
            Set<IPrototypedIngredient> parentDependencies,
377
            boolean collectMissingRecipes)
378
            throws RecursiveCraftingRecipeException {
379
        List<UnknownCraftingRecipeException> missingDependencies = Lists.newArrayList();
1✔
380
        List<CraftingJob> partialCraftingJobs = Lists.newArrayList();
1✔
381

382
        // Check if all requirements are met for this recipe, if so return directly (don't schedule yet)
383
        Pair<Map<IngredientComponent<?, ?>, List<?>>, Map<IngredientComponent<?, ?>, MissingIngredients<?, ?>>> simulation =
1✔
384
                getRecipeInputs(storageGetter, recipe, true, simulatedExtractionMemory, extractionMemoryReusable,
1✔
385
                        true, amount);
386
        Map<IngredientComponent<?, ?>, MissingIngredients<?, ?>> missingIngredients = simulation.getRight();
1✔
387
        if (!craftMissing && !missingIngredients.isEmpty()) {
1✔
388
            if (collectMissingRecipes) {
1✔
389
                // Collect missing ingredients as missing recipes when we don't want to craft sub-components,
390
                // but they are missing.
391
                for (Map.Entry<IngredientComponent<?, ?>, MissingIngredients<?, ?>> entry : missingIngredients.entrySet()) {
1✔
392
                    for (MissingIngredients.Element<?, ?> element : entry.getValue().getElements()) {
1✔
393
                        MissingIngredients.PrototypedWithRequested<?, ?> alternative = element.getAlternatives().get(0);
1✔
394

395
                        // Calculate the instance that was available in storage
396
                        IngredientComponent<?, ?> component = alternative.getRequestedPrototype().getComponent();
1✔
397
                        IIngredientMatcher matcher = component.getMatcher();
1✔
398
                        long storedQuantity = matcher.getQuantity(alternative.getRequestedPrototype().getPrototype()) - alternative.getQuantityMissing();
1✔
399
                        Map<IngredientComponent<?, ?>, List<?>> storageMap;
400
                        if (storedQuantity > 0) {
1✔
401
                            storageMap = Maps.newIdentityHashMap();
×
402
                            storageMap.put(component, Collections.singletonList(
×
403
                                    matcher.withQuantity(alternative.getRequestedPrototype().getPrototype(), storedQuantity)
×
404
                            ));
405
                        } else {
406
                            storageMap = Collections.emptyMap();
1✔
407
                        }
408

409
                        missingDependencies.add(new UnknownCraftingRecipeException(
1✔
410
                                alternative.getRequestedPrototype(), alternative.getQuantityMissing(),
1✔
411
                                Collections.emptyList(), compressMixedIngredients(new MixedIngredients(storageMap)), Lists.newArrayList()));
1✔
412
                    }
1✔
413
                }
1✔
414
            }
415
            return new PartialCraftingJobCalculation(null, missingDependencies, simulation.getLeft(), null);
1✔
416
        }
417

418
        // For all missing ingredients, recursively call this method for all missing items, and add as dependencies
419
        // We store dependencies as a mapping from recipe to job,
420
        // so that only one job exist per unique recipe,
421
        // so that a job amount can be incremented once another equal recipe is found.
422
        Map<IRecipeDefinition, CraftingJob> dependencies = Maps.newHashMapWithExpectedSize(missingIngredients.size());
1✔
423
        Map<IngredientComponent<?, ?>, IngredientCollectionPrototypeMap<?, ?>> dependenciesOutputSurplus = Maps.newIdentityHashMap();
1✔
424
        // We must be able to find crafting jobs for all dependencies
425
        for (IngredientComponent dependencyComponent : missingIngredients.keySet()) {
1✔
426
            try {
427
                // TODO: if we run into weird simulated extraction bugs, we may have to scope simulatedExtractionMemory, but I'm not sure about this (yet)
428
                PartialCraftingJobCalculationDependency resultDependency = calculateCraftingJobDependencyComponent(
1✔
429
                        dependencyComponent, dependenciesOutputSurplus, missingIngredients.get(dependencyComponent), parentDependencies,
1✔
430
                        dependencies, recipeIndex, channel, storageGetter, simulatedExtractionMemory, extractionMemoryReusable,
431
                        identifierGenerator, craftingJobsGraph, collectMissingRecipes);
432
                // Don't check the other components once we have an invalid dependency.
433
                if (!resultDependency.isValid()) {
1✔
434
                    missingDependencies.addAll(resultDependency.getUnknownCrafingRecipes());
1✔
435
                    partialCraftingJobs.addAll(resultDependency.getPartialCraftingJobs());
1✔
436
                    if (!collectMissingRecipes) {
1✔
437
                        break;
1✔
438
                    }
439
                }
440
            } catch (RecursiveCraftingRecipeException e) {
1✔
441
                e.addRecipe(recipe);
1✔
442
                throw e;
1✔
443
            }
1✔
444
        }
1✔
445
        // Add remaining surplus as negatives to simulated extraction
446
        for (IngredientComponent<?, ?> surplusComponent : dependenciesOutputSurplus.keySet()) {
1✔
447
            IngredientCollectionPrototypeMap<?, ?> surplusInstances = dependenciesOutputSurplus.get(surplusComponent);
1✔
448
            if (surplusInstances != null) {
1✔
449
                for (Object instance : surplusInstances) {
1✔
450
                    IngredientCollectionPrototypeMap<?, ?> simulatedExtractionMemoryInstances = simulatedExtractionMemory.get(surplusComponent);
1✔
451
                    if (simulatedExtractionMemoryInstances == null) {
1✔
452
                        simulatedExtractionMemoryInstances = new IngredientCollectionPrototypeMap<>(surplusComponent, true);
1✔
453
                        simulatedExtractionMemory.put(surplusComponent, simulatedExtractionMemoryInstances);
1✔
454
                    }
455
                    ((IngredientCollectionPrototypeMap) simulatedExtractionMemoryInstances).remove(instance);
1✔
456
                }
1✔
457
            }
458
        }
1✔
459

460
        // If at least one of our dependencies does not have a valid recipe or is not available,
461
        // go check the next recipe.
462
        if (!missingDependencies.isEmpty()) {
1✔
463
            return new PartialCraftingJobCalculation(null, missingDependencies, simulation.getLeft(), partialCraftingJobs);
1✔
464
        }
465

466
        CraftingJob craftingJob = new CraftingJob(identifierGenerator.getNext(), channel, recipe, amount,
1✔
467
                compressMixedIngredients(new MixedIngredients(simulation.getLeft())));
1✔
468
        for (CraftingJob dependency : dependencies.values()) {
1✔
469
            craftingJob.addDependency(dependency);
1✔
470
            craftingJobsGraph.addDependency(craftingJob, dependency);
1✔
471
        }
1✔
472
        return new PartialCraftingJobCalculation(craftingJob, null, simulation.getLeft(), null);
1✔
473
    }
474

475
    // Helper function for calculateCraftingJobs, returns a list of non-craftable ingredients and craftable ingredients.
476
    protected static <T, M> PartialCraftingJobCalculationDependency calculateCraftingJobDependencyComponent(
477
            IngredientComponent<T, M> dependencyComponent,
478
            Map<IngredientComponent<?, ?>, IngredientCollectionPrototypeMap<?, ?>> dependenciesOutputSurplus,
479
            MissingIngredients<T, M> missingIngredients,
480
            Set<IPrototypedIngredient> parentDependencies,
481
            Map<IRecipeDefinition, CraftingJob> dependencies,
482
            IRecipeIndex recipeIndex,
483
            int channel,
484
            Function<IngredientComponent<?, ?>, IIngredientComponentStorage> storageGetter,
485
            Map<IngredientComponent<?, ?>,
486
                    IngredientCollectionPrototypeMap<?, ?>> simulatedExtractionMemory,
487
            Map<IngredientComponent<?, ?>,
488
                    IIngredientCollectionMutable<?, ?>> extractionMemoryReusable,
489
            IIdentifierGenerator identifierGenerator,
490
            CraftingJobDependencyGraph craftingJobsGraph,
491
            boolean collectMissingRecipes)
492
            throws RecursiveCraftingRecipeException {
493
        IIngredientMatcher<T, M> dependencyMatcher = dependencyComponent.getMatcher();
1✔
494
        List<UnknownCraftingRecipeException> missingDependencies = Lists.newArrayList();
1✔
495
        for (MissingIngredients.Element<T, M> missingElement : missingIngredients.getElements()) {
1✔
496
            CraftingJob dependency = null;
1✔
497
            T dependencyInstance = null;
1✔
498
            boolean skipDependency = false;
1✔
499
            UnknownCraftingRecipeException firstError = null;
1✔
500
            // Loop over all prototype alternatives, at least one has to match.
501
            for (MissingIngredients.PrototypedWithRequested<T, M> prototypedAlternative : missingElement.getAlternatives()) {
1✔
502
                // Check if the missing element is reusable, and was triggered for craft earlier.
503
                if (missingElement.isInputReusable() && ((IIngredientCollectionMutable<T, M>) extractionMemoryReusable.get(dependencyComponent))
1✔
504
                        .contains(prototypedAlternative.getRequestedPrototype().getPrototype())) {
1✔
505
                    // Nothing has to be crafted anymore, jump to next dependency
506
                    skipDependency = true;
×
507
                    break;
×
508
                }
509

510
                IPrototypedIngredient<T, M> prototype = new PrototypedIngredient<>(
1✔
511
                        dependencyComponent,
512
                        dependencyMatcher.withQuantity(prototypedAlternative.getRequestedPrototype().getPrototype(), prototypedAlternative.getQuantityMissing()),
1✔
513
                        prototypedAlternative.getRequestedPrototype().getCondition()
1✔
514
                );
515
                // First check if we can grab it from previous surplus
516
                IngredientCollectionPrototypeMap<T, M> dependencyComponentSurplusOld = (IngredientCollectionPrototypeMap<T, M>) dependenciesOutputSurplus.get(dependencyComponent);
1✔
517
                IngredientCollectionPrototypeMap<T, M> dependencyComponentSurplus = null;
1✔
518
                if (dependencyComponentSurplusOld != null) {
1✔
519
                    // First create a copy of the given surplus store,
520
                    // and only once we see that the prototype is valid,
521
                    // save this copy again.
522
                    // This is because if this prototype is invalid,
523
                    // then we don't want these invalid surpluses.
524
                    dependencyComponentSurplus = new IngredientCollectionPrototypeMap<>(dependencyComponentSurplusOld.getComponent(), true);
1✔
525
                    dependencyComponentSurplus.addAll(dependencyComponentSurplusOld);
1✔
526

527
                    long remainingQuantity = dependencyMatcher.getQuantity(prototype.getPrototype());
1✔
528
                    IIngredientMatcher<T, M> prototypeMatcher = prototype.getComponent().getMatcher();
1✔
529
                    // Check all instances in the surplus that match with the given prototype
530
                    // For each match, we subtract its quantity from the required quantity.
531
                    Iterator<T> surplusIt = dependencyComponentSurplus.iterator(prototype.getPrototype(),
1✔
532
                            prototypeMatcher.withoutCondition(prototype.getCondition(), prototype.getComponent().getPrimaryQuantifier().getMatchCondition()));
1✔
533
                    boolean updatedRemainingQuantity = false;
1✔
534
                    while (remainingQuantity > 0 && surplusIt.hasNext()) {
1✔
535
                        updatedRemainingQuantity = true;
1✔
536
                        T matchingInstance = surplusIt.next();
1✔
537
                        long matchingInstanceQuantity = dependencyMatcher.getQuantity(matchingInstance);
1✔
538
                        if (matchingInstanceQuantity <= remainingQuantity) {
1✔
539
                            // This whole surplus instance can be consumed
540
                            remainingQuantity -= matchingInstanceQuantity;
1✔
541
                            surplusIt.remove();
1✔
542
                        } else {
543
                            // Only part of this surplus instance can be consumed.
544
                            matchingInstanceQuantity -= remainingQuantity;
×
545
                            remainingQuantity = 0;
×
546
                            surplusIt.remove();
×
547
                            dependencyComponentSurplus.setQuantity(matchingInstance, matchingInstanceQuantity);
×
548
                        }
549
                    }
1✔
550
                    if (updatedRemainingQuantity) {
1✔
551
                        if (remainingQuantity == 0) {
1✔
552
                            // The prototype is valid,
553
                            // so we can finally store our temporary surplus
554
                            dependenciesOutputSurplus.put(dependencyComponent, dependencyComponentSurplus);
×
555

556
                            // Nothing has to be crafted anymore, jump to next dependency
557
                            skipDependency = true;
×
558
                            break;
×
559
                        } else {
560
                            // Partial availability, other part needs to be crafted still.
561
                            prototype = new PrototypedIngredient<>(dependencyComponent,
1✔
562
                                    dependencyMatcher.withQuantity(prototype.getPrototype(), remainingQuantity),
1✔
563
                                    prototype.getCondition());
1✔
564
                        }
565
                    }
566
                }
567

568
                // Try to craft the given prototype
569
                try {
570
                    Set<IPrototypedIngredient> childDependencies = Sets.newHashSet(parentDependencies);
1✔
571
                    IPrototypedIngredient<T, M> dependencyPrototype = prototype;
1✔
572
                    if (dependencyMatcher.getQuantity(dependencyPrototype.getPrototype()) != 1) {
1✔
573
                        // Ensure 1-quantity is stored, for proper comparisons in future calls.
574
                        dependencyPrototype = new PrototypedIngredient<>(dependencyComponent,
1✔
575
                                dependencyMatcher.withQuantity(prototype.getPrototype(), 1),
1✔
576
                                prototype.getCondition());
1✔
577
                    }
578
                    if (!childDependencies.add(dependencyPrototype)) {
1✔
579
                        throw new RecursiveCraftingRecipeException(prototype);
1✔
580
                    }
581

582
                    dependency = calculateCraftingJobs(recipeIndex, channel, storageGetter,
1✔
583
                            dependencyComponent, prototype.getPrototype(),
1✔
584
                            prototype.getCondition(), true, simulatedExtractionMemory, extractionMemoryReusable,
1✔
585
                            identifierGenerator, craftingJobsGraph, childDependencies, collectMissingRecipes);
586
                    dependencyInstance = prototype.getPrototype();
1✔
587

588
                    // The prototype is valid,
589
                    // so we can finally store our temporary surplus
590
                    if (dependencyComponentSurplus != null) {
1✔
591
                        dependenciesOutputSurplus.put(dependencyComponent, dependencyComponentSurplus);
1✔
592
                    }
593

594
                    // Add the auxiliary recipe outputs that are not requested to the surplus
595
                    Object dependencyQuantifierlessCondition = dependencyMatcher.withoutCondition(prototype.getCondition(),
1✔
596
                            dependencyComponent.getPrimaryQuantifier().getMatchCondition());
1✔
597
                    long requestedQuantity = dependencyMatcher.getQuantity(prototype.getPrototype());
1✔
598
                    for (IngredientComponent outputComponent : dependency.getRecipe().getOutput().getComponents()) {
1✔
599
                        IngredientCollectionPrototypeMap<?, ?> componentSurplus = dependenciesOutputSurplus.get(outputComponent);
1✔
600
                        if (componentSurplus == null) {
1✔
601
                            componentSurplus = new IngredientCollectionPrototypeMap<>(outputComponent, true);
1✔
602
                            dependenciesOutputSurplus.put(outputComponent, componentSurplus);
1✔
603
                        }
604
                        List<Object> instances = dependency.getRecipe().getOutput().getInstances(outputComponent);
1✔
605
                        long recipeAmount = dependency.getAmount();
1✔
606
                        if (recipeAmount > 1) {
1✔
607
                            IIngredientMatcher matcher = outputComponent.getMatcher();
1✔
608
                            // If more than one recipe amount was crafted, correctly multiply the outputs to calculate the effective surplus.
609
                            instances = instances
1✔
610
                                    .stream()
1✔
611
                                    .map(instance -> matcher.withQuantity(instance, matcher.getQuantity(instance) * recipeAmount))
1✔
612
                                    .collect(Collectors.toList());
1✔
613
                        }
614
                        addRemainderAsSurplusForComponent(outputComponent, (List) instances, componentSurplus,
1✔
615
                                (IngredientComponent) prototype.getComponent(), prototype.getPrototype(), dependencyQuantifierlessCondition,
1✔
616
                                requestedQuantity);
617
                    }
1✔
618

619
                    break;
1✔
620
                } catch (UnknownCraftingRecipeException e) {
1✔
621
                    // Save the first error, and check the next prototype
622
                    if (firstError == null) {
1✔
623
                        // Modify the error so that the correct missing quantity is stored
624
                        firstError = new UnknownCraftingRecipeException(
1✔
625
                                e.getIngredient(), prototypedAlternative.getQuantityMissing(),
1✔
626
                                e.getMissingChildRecipes(), compressMixedIngredients(e.getIngredientsStorage()), e.getPartialCraftingJobs());
1✔
627
                    }
628
                }
629
            }
1✔
630

631
            // Check if this dependency can be skipped
632
            if (skipDependency) {
1✔
633
                continue;
×
634
            }
635

636
            // If no valid crafting recipe was found for the current sub-instance, re-throw its error
637
            if (dependency == null) {
1✔
638
                missingDependencies.add(firstError);
1✔
639
                if (collectMissingRecipes) {
1✔
640
                    continue;
1✔
641
                } else {
642
                    break;
643
                }
644
            }
645

646
            // --- When we reach this point, a valid sub-recipe was found ---
647

648
            // Update our simulatedExtractionMemory to indicate that the dependency's
649
            // instance should not be extracted anymore.
650
            ((IngredientCollectionPrototypeMap<T, M>) simulatedExtractionMemory.get(dependencyComponent))
1✔
651
                    .remove(dependencyInstance);
1✔
652

653
            // If the dependency instance is reusable, mark it as available in our reusable extraction memory
654
            if (missingElement.isInputReusable()) {
1✔
655
                ((IIngredientCollectionMutable<T, M>) extractionMemoryReusable.get(dependencyComponent)).add(dependencyInstance);
1✔
656
            }
657

658
            // Add the valid sub-recipe it to our dependencies
659
            // If the recipe was already present at this level, just increment the amount of the existing job.
660
            CraftingJob existingJob = dependencies.get(dependency.getRecipe());
1✔
661
            if (existingJob == null) {
1✔
662
                dependencies.put(dependency.getRecipe(), dependency);
1✔
663
            } else {
664
                // Remove the standalone dependency job by merging it into the existing job
665
                craftingJobsGraph.mergeCraftingJobs(existingJob, dependency, true);
1✔
666
            }
667
        }
1✔
668

669
        return new PartialCraftingJobCalculationDependency(missingDependencies, dependencies.values());
1✔
670
    }
671

672
    // Helper function for calculateCraftingJobDependencyComponent
673
    protected static <T1, M1, T2, M2> void addRemainderAsSurplusForComponent(IngredientComponent<T1, M1> ingredientComponent,
674
                                                                             List<T1> instances,
675
                                                                             IngredientCollectionPrototypeMap<T1, M1> simulatedExtractionMemory,
676
                                                                             IngredientComponent<T2, M2> blackListComponent,
677
                                                                             T2 blacklistInstance, M2 blacklistCondition,
678
                                                                             long blacklistQuantity) {
679
        IIngredientMatcher<T2, M2> blacklistMatcher = blackListComponent.getMatcher();
1✔
680
        for (T1 instance : instances) {
1✔
681
            IIngredientMatcher<T1, M1> outputMatcher = ingredientComponent.getMatcher();
1✔
682
            long reduceQuantity = 0;
1✔
683
            if (blackListComponent == ingredientComponent
1✔
684
                    && blacklistMatcher.matches(blacklistInstance, (T2) instance, blacklistCondition)) {
1✔
685
                reduceQuantity = blacklistQuantity;
1✔
686
            }
687
            long quantity = simulatedExtractionMemory.getQuantity(instance) + (outputMatcher.getQuantity(instance) - reduceQuantity);
1✔
688
            if (quantity > 0) {
1✔
689
                simulatedExtractionMemory.setQuantity(instance, quantity);
1✔
690
            }
691
        }
1✔
692
    }
1✔
693

694
    /**
695
     * Schedule all crafting jobs in the given dependency graph in the given network.
696
     *
697
     * @param craftingNetwork            The target crafting network.
698
     * @param storageGetter              The storage getter.
699
     * @param craftingJobDependencyGraph The crafting job dependency graph.
700
     * @param allowDistribution          If the crafting jobs are allowed to be split over multiple crafting interfaces.
701
     * @param initiator                  Optional UUID of the initiator.
702
     * @throws UnavailableCraftingInterfacesException If no crafting interfaces were available.
703
     */
704
    public static void scheduleCraftingJobs(ICraftingNetwork craftingNetwork,
705
                                            Function<IngredientComponent<?, ?>, IIngredientComponentStorage> storageGetter,
706
                                            CraftingJobDependencyGraph craftingJobDependencyGraph,
707
                                            boolean allowDistribution,
708
                                            @Nullable UUID initiator) throws UnavailableCraftingInterfacesException {
709
        List<CraftingJob> startedJobs = Lists.newArrayList();
×
710
        craftingNetwork.getCraftingJobDependencyGraph().importDependencies(craftingJobDependencyGraph);
×
711
        for (CraftingJob craftingJob : craftingJobDependencyGraph.getCraftingJobs()) {
×
712
            try {
713
                craftingNetwork.scheduleCraftingJob(craftingJob, allowDistribution, storageGetter);
×
714
            } catch (UnavailableCraftingInterfacesException e) {
×
715
                // First, cancel all jobs that were already started
716
                for (CraftingJob startedJob : startedJobs) {
×
717
                    CraftingHelpers.insertIngredientsGuaranteed(startedJob.getIngredientsStorageBuffer(), storageGetter, (ICraftingResultsSink) Iterables.getFirst(craftingNetwork.getCraftingInterfaces(startedJob.getChannel()), null));
×
718
                    craftingNetwork.cancelCraftingJob(startedJob.getChannel(), startedJob.getId());
×
719
                }
×
720

721
                // Then, throw an exception for all jobs in this dependency graph
722
                throw new UnavailableCraftingInterfacesException(craftingJobDependencyGraph.getCraftingJobs());
×
723
            }
×
724
            startedJobs.add(craftingJob);
×
725
            if (initiator != null) {
×
726
                craftingJob.setInitiatorUuid(initiator.toString());
×
727
            }
728
        }
×
729
    }
×
730

731
    /**
732
     * Schedule the given crafting job  in the given network.
733
     *
734
     * @param craftingNetwork   The target crafting network.
735
     * @param storageGetter     The storage getter.
736
     * @param craftingJob       The crafting job to schedule.
737
     * @param allowDistribution If the crafting job is allowed to be split over multiple crafting interfaces.
738
     * @param initiator         Optional UUID of the initiator.
739
     * @return The scheduled crafting job.
740
     * @throws UnavailableCraftingInterfacesException If no crafting interfaces were available.
741
     */
742
    public static CraftingJob scheduleCraftingJob(ICraftingNetwork craftingNetwork,
743
                                                  Function<IngredientComponent<?, ?>, IIngredientComponentStorage> storageGetter,
744
                                                  CraftingJob craftingJob,
745
                                                  boolean allowDistribution,
746
                                                  @Nullable UUID initiator) throws UnavailableCraftingInterfacesException {
747
        craftingNetwork.scheduleCraftingJob(craftingJob, allowDistribution, storageGetter);
×
748
        if (initiator != null) {
×
749
            craftingJob.setInitiatorUuid(initiator.toString());
×
750
        }
751
        return craftingJob;
×
752
    }
753

754
    /**
755
     * Schedule a crafting job for the given instance in the given network.
756
     * @param network The target network.
757
     * @param channel The target channel.
758
     * @param ingredientComponent The ingredient component type of the instance.
759
     * @param instance The instance to craft.
760
     * @param matchCondition The match condition of the instance.
761
     * @param craftMissing If the missing required ingredients should also be crafted.
762
     * @param allowDistribution If the crafting job is allowed to be split over multiple crafting interfaces.
763
     * @param identifierGenerator An ID generator for crafting jobs.
764
     * @param initiator Optional UUID of the initiator.
765
     * @param <T> The instance type.
766
     * @param <M> The matching condition parameter.
767
     * @return The scheduled crafting job, or null if no recipe was found.
768
     */
769
    @Nullable
770
    public static <T, M> CraftingJob calculateAndScheduleCraftingJob(INetwork network, int channel,
771
                                                                     IngredientComponent<T, M> ingredientComponent,
772
                                                                     T instance, M matchCondition,
773
                                                                     boolean craftMissing, boolean allowDistribution,
774
                                                                     IIdentifierGenerator identifierGenerator,
775
                                                                     @Nullable UUID initiator) {
776
        try {
777
            CraftingJobDependencyGraph dependencyGraph = new CraftingJobDependencyGraph();
×
778
            CraftingJob craftingJob = calculateCraftingJobs(network, channel, ingredientComponent, instance,
×
779
                    matchCondition, craftMissing, identifierGenerator, dependencyGraph, false);
780

781
            ICraftingNetwork craftingNetwork = getCraftingNetworkChecked(network);
×
782

783
            scheduleCraftingJobs(craftingNetwork, getNetworkStorageGetter(network, channel, false), dependencyGraph, allowDistribution, initiator);
×
784

785
            return craftingJob;
×
786
        } catch (UnknownCraftingRecipeException | RecursiveCraftingRecipeException | UnavailableCraftingInterfacesException e) {
×
787
            return null;
×
788
        }
789
    }
790

791
    /**
792
     * Schedule a crafting job for the given recipe in the given network.
793
     * @param network The target network.
794
     * @param channel The target channel.
795
     * @param recipe The recipe to craft.
796
     * @param amount The amount to craft.
797
     * @param craftMissing If the missing required ingredients should also be crafted.
798
     * @param allowDistribution If the crafting job is allowed to be split over multiple crafting interfaces.
799
     * @param identifierGenerator An ID generator for crafting jobs.
800
     * @param initiator Optional UUID of the initiator.
801
     * @return The scheduled crafting job, or null if no recipe was found.
802
     */
803
    @Nullable
804
    public static CraftingJob calculateAndScheduleCraftingJob(INetwork network, int channel,
805
                                                              IRecipeDefinition recipe, int amount,
806
                                                              boolean craftMissing, boolean allowDistribution,
807
                                                              IIdentifierGenerator identifierGenerator,
808
                                                              @Nullable UUID initiator) {
809
        try {
810
            CraftingJobDependencyGraph dependencyGraph = new CraftingJobDependencyGraph();
×
811
            CraftingJob craftingJob = calculateCraftingJobs(network, channel, recipe, amount, craftMissing,
×
812
                    identifierGenerator, dependencyGraph, false);
813

814
            ICraftingNetwork craftingNetwork = getCraftingNetworkChecked(network);
×
815

816
            scheduleCraftingJobs(craftingNetwork, getNetworkStorageGetter(network, channel, false), dependencyGraph, allowDistribution, initiator);
×
817

818
            return craftingJob;
×
819
        } catch (RecursiveCraftingRecipeException | FailedCraftingRecipeException | UnavailableCraftingInterfacesException e) {
×
820
            return null;
×
821
        }
822
    }
823

824
    /**
825
     * Check if the given network contains the given instance in any of its storages.
826
     * @param network The target network.
827
     * @param channel The target channel.
828
     * @param ingredientComponent The ingredient component type of the instance.
829
     * @param instance The instance to check.
830
     * @param matchCondition The match condition of the instance.
831
     * @param <T> The instance type.
832
     * @param <M> The matching condition parameter.
833
     * @return If the instance is present in the network.
834
     */
835
    public static <T, M> boolean hasStorageInstance(INetwork network, int channel,
836
                                                    IngredientComponent<T, M> ingredientComponent,
837
                                                    T instance, M matchCondition) {
838
        IIngredientComponentStorage<T, M> storage = getNetworkStorage(network, channel, ingredientComponent, true);
×
839
        if (storage instanceof IngredientChannelAdapter) ((IngredientChannelAdapter) storage).disableLimits();
×
840
        boolean contains;
841
        if (storage instanceof IngredientChannelIndexed) {
×
842
            IIngredientMatcher<T, M> matcher = ingredientComponent.getMatcher();
×
843
            long quantityPresent = ((IngredientChannelIndexed<T, M>) storage).getIndex().getQuantity(instance);
×
844
            contains = matcher.hasCondition(matchCondition, ingredientComponent.getPrimaryQuantifier().getMatchCondition()) ? quantityPresent >= matcher.getQuantity(instance) : quantityPresent > 0;
×
845
        } else {
×
846
            contains = !ingredientComponent.getMatcher().isEmpty(storage.extract(instance, matchCondition, true));
×
847
        }
848
        if (storage instanceof IngredientChannelAdapter) ((IngredientChannelAdapter) storage).enableLimits();
×
849
        return contains;
×
850
    }
851

852
    /**
853
     * Check the quantity of the given instance in the network.
854
     * @param network The target network.
855
     * @param channel The target channel.
856
     * @param ingredientComponent The ingredient component type of the instance.
857
     * @param instance The instance to check.
858
     * @param matchCondition The match condition of the instance.
859
     * @param <T> The instance type.
860
     * @param <M> The matching condition parameter.
861
     * @return The quantity in the network.
862
     */
863
    public static <T, M> long getStorageInstanceQuantity(INetwork network, int channel,
864
                                                         IngredientComponent<T, M> ingredientComponent,
865
                                                         T instance, M matchCondition) {
866
        IIngredientComponentStorage<T, M> storage = getNetworkStorage(network, channel, ingredientComponent, true);
×
867
        if (storage instanceof IngredientChannelAdapter) ((IngredientChannelAdapter) storage).disableLimits();
×
868
        long quantityPresent;
869
        if (storage instanceof IngredientChannelIndexed) {
×
870
            quantityPresent = ((IngredientChannelIndexed<T, M>) storage).getIndex().getQuantity(instance);
×
871
        } else {
872
            quantityPresent = ingredientComponent.getMatcher().getQuantity(storage.extract(instance, matchCondition, true));
×
873
        }
874
        if (storage instanceof IngredientChannelAdapter) ((IngredientChannelAdapter) storage).enableLimits();
×
875
        return quantityPresent;
×
876
    }
877

878
    /**
879
     * Check if there is a scheduled crafting job for the given instance.
880
     * @param craftingNetwork The target crafting network.
881
     * @param channel The target channel.
882
     * @param ingredientComponent The ingredient component type of the instance.
883
     * @param instance The instance to check.
884
     * @param matchCondition The match condition of the instance.
885
     * @param <T> The instance type.
886
     * @param <M> The matching condition parameter.
887
     * @return If the instance has a crafting job.
888
     */
889
    public static <T, M> boolean isCrafting(ICraftingNetwork craftingNetwork, int channel,
890
                                            IngredientComponent<T, M> ingredientComponent,
891
                                            T instance, M matchCondition) {
892
        Iterator<CraftingJob> craftingJobs = craftingNetwork.getCraftingJobs(channel, ingredientComponent,
×
893
                instance, matchCondition);
894
        return craftingJobs.hasNext();
×
895
    }
896

897
    /**
898
     * Check if there is a scheduled crafting job for the given recipe.
899
     * @param craftingNetwork The target crafting network.
900
     * @param channel The target channel.
901
     * @param recipe The recipe to check.
902
     * @return If the instance has a crafting job.
903
     */
904
    public static boolean isCrafting(ICraftingNetwork craftingNetwork, int channel,
905
                                     IRecipeDefinition recipe) {
906
        Iterator<CraftingJob> it = craftingNetwork.getCraftingJobs(channel);
×
907
        while (it.hasNext()) {
×
908
            if (it.next().getRecipe().equals(recipe)) {
×
909
                return true;
×
910
            }
911
        }
912
        return false;
×
913
    }
914

915
    /**
916
     * Get all required recipe input ingredients from the network for the given ingredient component.
917
     *
918
     * If multiple alternative inputs are possible,
919
     * then only the first possible match will be taken.
920
     *
921
     * Note: Make sure that you first call in simulation-mode
922
     * to see if the ingredients are available.
923
     * If you immediately call this non-simulated,
924
     * then there might be a chance that ingredients are lost
925
     * from the network.
926
     *
927
     * @param storage The target storage.
928
     * @param ingredientComponent The ingredient component to get the ingredients for.
929
     * @param recipe The recipe to get the inputs from.
930
     * @param simulate If true, then the ingredients will effectively be removed from the network, not when false.
931
     * @param recipeOutputQuantity The number of times the given recipe should be applied.
932
     * @param <T> The instance type.
933
     * @param <M> The matching condition parameter, may be Void.
934
     * @return A list of slot-based ingredients, or null if no valid inputs could be found.
935
     */
936
    @Nullable
937
    public static <T, M> List<T> getIngredientRecipeInputs(IIngredientComponentStorage<T, M> storage,
938
                                                           IngredientComponent<T, M> ingredientComponent,
939
                                                           IRecipeDefinition recipe, boolean simulate,
940
                                                           long recipeOutputQuantity) {
941
        return getIngredientRecipeInputs(storage, ingredientComponent, recipe, simulate,
1✔
942
                simulate ? new IngredientCollectionPrototypeMap<>(ingredientComponent, true) : null,
1✔
943
                new IngredientHashSet<>(ingredientComponent),
944
                false, recipeOutputQuantity).getLeft();
1✔
945
    }
946

947
    /**
948
     * Get all required recipe input ingredients from the network for the given ingredient component,
949
     * and optionally, explicitly calculate the missing ingredients.
950
     *
951
     * If multiple alternative inputs are possible,
952
     * then only the first possible match will be taken.
953
     *
954
     * Note: Make sure that you first call in simulation-mode
955
     * to see if the ingredients are available.
956
     * If you immediately call this non-simulated,
957
     * then there might be a chance that ingredients are lost
958
     * from the network.
959
     *
960
     * @param storage The target storage.
961
     * @param ingredientComponent The ingredient component to get the ingredients for.
962
     * @param recipe The recipe to get the inputs from.
963
     * @param simulate If true, then the ingredients will effectively be removed from the network, not when false.
964
     * @param simulatedExtractionMemory This map remembers all extracted instances in simulation mode.
965
     *                                  This is to make sure that instances can not be extracted multiple times
966
     *                                  when simulating.
967
     *                                  The quantities can also go negatives,
968
     *                                  which means that a surplus of the given instance is present,
969
     *                                  which will be used up first before a call to the storage.
970
     * @param extractionMemoryReusable Like simulatedExtractionMemory, but it stores the reusable ingredients.
971
     * @param collectMissingIngredients If missing ingredients should be collected.
972
     *                                  If false, then the first returned list may be null
973
     *                                  if no valid matches can be found,
974
     *                                  and the second returned list is always null,
975
     * @param recipeOutputQuantity The number of times the given recipe should be applied.
976
     * @param <T> The instance type.
977
     * @param <M> The matching condition parameter, may be Void.
978
     * @return A pair with two lists:
979
     *           1. A list of available slot-based ingredients.
980
     *           2. A missing ingredients object.
981
     */
982
    public static <T, M> Pair<List<T>, MissingIngredients<T, M>>
983
    getIngredientRecipeInputs(IIngredientComponentStorage<T, M> storage, IngredientComponent<T, M> ingredientComponent,
984
                              IRecipeDefinition recipe, boolean simulate,
985
                              IngredientCollectionPrototypeMap<T, M> simulatedExtractionMemory,
986
                              IIngredientCollectionMutable<T, M> extractionMemoryReusable,
987
                              boolean collectMissingIngredients, long recipeOutputQuantity) {
988
        return getIngredientRecipeInputs(storage, ingredientComponent, recipe, simulate, simulatedExtractionMemory,
1✔
989
                extractionMemoryReusable, collectMissingIngredients, recipeOutputQuantity, false);
990
    }
991

992
    public static <T, M> Pair<List<T>, MissingIngredients<T, M>>
993
    getIngredientRecipeInputs(IIngredientComponentStorage<T, M> storage, IngredientComponent<T, M> ingredientComponent,
994
                              IRecipeDefinition recipe, boolean simulate,
995
                              IngredientCollectionPrototypeMap<T, M> simulatedExtractionMemory,
996
                              IIngredientCollectionMutable<T, M> extractionMemoryReusable,
997
                              boolean collectMissingIngredients, long recipeOutputQuantity,
998
                              boolean skipReusableIngredients) {
999
        IIngredientMatcher<T, M> matcher = ingredientComponent.getMatcher();
1✔
1000

1001
        // Quickly return if the storage is empty
1002
        // We can't take this shortcut if we have a reusable ingredient AND extractionMemoryReusable is not empty
1003
        if (storage.getMaxQuantity() == 0 &&
1✔
1004
                extractionMemoryReusable.isEmpty() &&
1✔
1005
                IntStream.range(0, recipe.getInputs(ingredientComponent).size())
1✔
1006
                        .noneMatch(i -> recipe.isInputReusable(ingredientComponent, i))) {
1✔
1007
            if (collectMissingIngredients) {
1✔
1008
                List<IPrototypedIngredientAlternatives<T, M>> recipeInputs = recipe.getInputs(ingredientComponent);
1✔
1009
                MissingIngredients<T, M> missing = new MissingIngredients<>(recipeInputs.stream().map(IPrototypedIngredientAlternatives::getAlternatives)
1✔
1010
                        .map(l -> multiplyPrototypedIngredients(l, recipeOutputQuantity)) // If the input is reusable, don't multiply the expected input quantity
1✔
1011
                        .map(ps -> new MissingIngredients.Element<>(ps
1✔
1012
                                .stream()
1✔
1013
                                .map(p -> new MissingIngredients.PrototypedWithRequested<>(p, matcher.getQuantity(p.getPrototype())))
1✔
1014
                                .collect(Collectors.toList()), false)
1✔
1015
                        )
1016
                        .collect(Collectors.toList()));
1✔
1017
                return Pair.of(
1✔
1018
                        Lists.newArrayList(Collections.nCopies(recipe.getInputs(ingredientComponent).size(),
1✔
1019
                                ingredientComponent.getMatcher().getEmptyInstance())),
1✔
1020
                        missing);
1021
            } else {
1022
                return Pair.of(null, null);
1✔
1023
            }
1024
        }
1025

1026
        // Iterate over all input slots
1027
        List<IPrototypedIngredientAlternatives<T, M>> inputAlternativePrototypes = recipe.getInputs(ingredientComponent);
1✔
1028
        List<T> inputInstances = Lists.newArrayList();
1✔
1029
        List<MissingIngredients.Element<T, M>> missingElements =
1030
                collectMissingIngredients ? Lists.newArrayList() : null;
1✔
1031
        for (int inputIndex = 0; inputIndex < inputAlternativePrototypes.size(); inputIndex++) {
1✔
1032
            IPrototypedIngredientAlternatives<T, M> inputPrototypes = inputAlternativePrototypes.get(inputIndex);
1✔
1033

1034
            // If reusable ingredients should be skipped, treat them as empty (not needed yet).
1035
            // This is used when a job has dependencies, so that reusable ingredients remain available
1036
            // for other jobs to use, and will be extracted lazily in the update loop.
1037
            if (skipReusableIngredients && recipe.isInputReusable(ingredientComponent, inputIndex)) {
1✔
1038
                inputInstances.add(matcher.getEmptyInstance());
×
1039
                continue;
×
1040
            }
1041

1042
            T firstInputInstance = null;
1✔
1043
            boolean setFirstInputInstance = false;
1✔
1044
            T inputInstance = null;
1✔
1045
            boolean hasInputInstance = false;
1✔
1046
            IngredientCollectionPrototypeMap<T, M> simulatedExtractionMemoryBufferFirst = null;
1✔
1047
            IIngredientCollectionMutable<T, M> extractionMemoryReusableBufferFirst = null;
1✔
1048

1049
            // Iterate over all alternatives for this input slot, and take the first matching ingredient.
1050
            List<MissingIngredients.PrototypedWithRequested<T, M>> missingAlternatives = Lists.newArrayList();
1✔
1051
            IngredientCollectionPrototypeMap<T, M> simulatedExtractionMemoryAlternative = simulate ? new IngredientCollectionPrototypeMap<>(ingredientComponent, true) : null;
1✔
1052
            if (simulate) {
1✔
1053
                simulatedExtractionMemoryAlternative.addAll(simulatedExtractionMemory);
1✔
1054
            }
1055
            for (IPrototypedIngredient<T, M> inputPrototype : inputPrototypes.getAlternatives()) {
1✔
1056
                boolean inputReusable = recipe.isInputReusable(ingredientComponent, inputIndex);
1✔
1057
                IngredientCollectionPrototypeMap<T, M> simulatedExtractionMemoryBuffer = simulate ? new IngredientCollectionPrototypeMap<>(ingredientComponent, true) : null;
1✔
1058
                IIngredientCollectionMutable<T, M> extractionMemoryReusableBuffer = inputReusable ? new IngredientHashSet<>(ingredientComponent) : null;
1✔
1059
                boolean shouldBreak = false;
1✔
1060

1061
                // Multiply required prototype if recipe quantity is higher than one, AND if the input is NOT reusable.
1062
                if (recipeOutputQuantity > 1 && !inputReusable) {
1✔
1063
                    inputPrototype = multiplyPrototypedIngredient(inputPrototype, recipeOutputQuantity);
1✔
1064
                }
1065

1066
                // If the prototype is empty, we can skip network extraction
1067
                if (matcher.isEmpty(inputPrototype.getPrototype())) {
1✔
1068
                    inputInstance = inputPrototype.getPrototype();
1✔
1069
                    hasInputInstance = true;
1✔
1070
                    break;
1✔
1071
                }
1072

1073
                long prototypeQuantity = matcher.getQuantity(inputPrototype.getPrototype());
1✔
1074
                if (inputReusable && extractionMemoryReusable.contains(inputPrototype.getPrototype())) {
1✔
1075
                    // If the reusable item has been extracted before, mark as valid, and don't extract again.
1076
                    inputInstance = inputPrototype.getComponent().getMatcher().getEmptyInstance();
1✔
1077
                    hasInputInstance = true;
1✔
1078
                    shouldBreak = true;
1✔
1079
                } else {
1080
                    long memoryQuantity;
1081
                    if (simulate && (memoryQuantity = simulatedExtractionMemoryAlternative
1✔
1082
                            .getQuantity(inputPrototype.getPrototype())) != 0) {
1✔
1083
                        long newQuantity = memoryQuantity + prototypeQuantity;
1✔
1084
                        if (newQuantity > 0) {
1✔
1085
                            // Part of our quantity can be provided via simulatedExtractionMemory,
1086
                            // but not all of it,
1087
                            // so we need to extract from storage as well.
1088
                            T newInstance = matcher.withQuantity(inputPrototype.getPrototype(), newQuantity);
1✔
1089
                            M matchCondition = matcher.withoutCondition(inputPrototype.getCondition(),
1✔
1090
                                    ingredientComponent.getPrimaryQuantifier().getMatchCondition());
1✔
1091
                            if (storage instanceof IngredientChannelAdapter)
1✔
1092
                                ((IngredientChannelAdapter) storage).disableLimits();
×
1093
                            T extracted = storage.extract(newInstance, matchCondition, true);
1✔
1094
                            if (storage instanceof IngredientChannelAdapter)
1✔
1095
                                ((IngredientChannelAdapter) storage).enableLimits();
×
1096
                            long quantityExtracted = matcher.getQuantity(extracted);
1✔
1097
                            if (quantityExtracted == newQuantity) {
1✔
1098
                                // All remaining could be extracted from storage, all is fine now
1099
                                inputInstance = inputPrototype.getPrototype();
1✔
1100
                                simulatedExtractionMemoryAlternative.add(inputInstance);
1✔
1101
                                simulatedExtractionMemoryBuffer.add(inputInstance);
1✔
1102
                                if (inputReusable) {
1✔
1103
                                    extractionMemoryReusableBuffer.add(inputInstance);
1✔
1104
                                }
1105
                                hasInputInstance = true;
1✔
1106
                                shouldBreak = true;
1✔
1107
                            } else if (collectMissingIngredients) {
1✔
1108
                                // Not everything could be extracted from storage, we *miss* the remaining ingredient.
1109
                                long quantityMissingPrevious = Math.max(0, memoryQuantity - quantityExtracted);
1✔
1110
                                long quantityMissingTotal = newQuantity - quantityExtracted;
1✔
1111
                                long quantityMissingRelative = quantityMissingTotal - quantityMissingPrevious;
1✔
1112

1113
                                missingAlternatives.add(new MissingIngredients.PrototypedWithRequested<>(inputPrototype, quantityMissingRelative));
1✔
1114
                                inputInstance = matcher.withQuantity(inputPrototype.getPrototype(), prototypeQuantity - quantityMissingRelative);
1✔
1115
                                simulatedExtractionMemoryAlternative.setQuantity(inputPrototype.getPrototype(), quantityMissingTotal);
1✔
1116
                                // Original prototype quantity because we what can be extracted from storage and what could NOT be extracted from storage should be added to the simulation extraction memory.
1117
                                // The part that could NOT be extracted will be removed later when crafting jobs are calculated for the missing elements. (not doing so would lead to over-estimation of what is in storage)
1118
                                simulatedExtractionMemoryBuffer.add(matcher.withQuantity(inputPrototype.getPrototype(), prototypeQuantity));
1✔
1119
                            }
1120
                        } else {
1✔
1121
                            // All of our quantity can be provided via our surplus in simulatedExtractionMemory
1122
                            simulatedExtractionMemoryAlternative.add(inputPrototype.getPrototype());
1✔
1123
                            simulatedExtractionMemoryBuffer.add(inputPrototype.getPrototype());
1✔
1124
                            if (inputReusable) {
1✔
1125
                                extractionMemoryReusableBuffer.add(inputPrototype.getPrototype());
1✔
1126
                            }
1127
                            inputInstance = inputPrototype.getComponent().getMatcher().getEmptyInstance();
1✔
1128
                            hasInputInstance = true;
1✔
1129
                            shouldBreak = true;
1✔
1130
                        }
1131
                    } else {
1✔
1132
                        M matchCondition = matcher.withoutCondition(inputPrototype.getCondition(),
1✔
1133
                                ingredientComponent.getPrimaryQuantifier().getMatchCondition());
1✔
1134
                        if (storage instanceof IngredientChannelAdapter)
1✔
1135
                            ((IngredientChannelAdapter) storage).disableLimits();
×
1136
                        T extracted = storage.extract(inputPrototype.getPrototype(), matchCondition, simulate);
1✔
1137
                        if (storage instanceof IngredientChannelAdapter)
1✔
1138
                            ((IngredientChannelAdapter) storage).enableLimits();
×
1139
                        long quantityExtracted = matcher.getQuantity(extracted);
1✔
1140
                        inputInstance = extracted;
1✔
1141
                        if (simulate) {
1✔
1142
                            simulatedExtractionMemoryAlternative.add(extracted);
1✔
1143
                            simulatedExtractionMemoryBuffer.add(inputPrototype.getPrototype());
1✔
1144
                        }
1145
                        if (prototypeQuantity == quantityExtracted) {
1✔
1146
                            hasInputInstance = true;
1✔
1147
                            shouldBreak = true;
1✔
1148
                            if (inputReusable) {
1✔
1149
                                extractionMemoryReusableBuffer.add(inputPrototype.getPrototype());
1✔
1150
                            }
1151
                        } else if (collectMissingIngredients) {
1✔
1152
                            long quantityMissing = prototypeQuantity - quantityExtracted;
1✔
1153
                            missingAlternatives.add(new MissingIngredients.PrototypedWithRequested<>(inputPrototype, quantityMissing));
1✔
1154
                        }
1155
                    }
1156
                }
1157

1158
                if (!setFirstInputInstance || shouldBreak) {
1✔
1159
                    setFirstInputInstance = true;
1✔
1160
                    firstInputInstance = inputInstance;
1✔
1161
                    simulatedExtractionMemoryBufferFirst = simulatedExtractionMemoryBuffer;
1✔
1162
                    if (inputReusable) {
1✔
1163
                        extractionMemoryReusableBufferFirst = extractionMemoryReusableBuffer;
1✔
1164
                    } else {
1165
                        extractionMemoryReusableBufferFirst = null;
1✔
1166
                    }
1167
                }
1168

1169
                if (shouldBreak) {
1✔
1170
                    break;
1✔
1171
                }
1172
            }
1✔
1173

1174
            if (simulatedExtractionMemoryBufferFirst != null) {
1✔
1175
                for (T instance : simulatedExtractionMemoryBufferFirst) {
1✔
1176
                    simulatedExtractionMemory.add(instance);
1✔
1177
                }
1✔
1178
            }
1179
            if (extractionMemoryReusableBufferFirst != null) {
1✔
1180
                for (T instance : extractionMemoryReusableBufferFirst) {
1✔
1181
                    extractionMemoryReusable.add(instance);
1✔
1182
                }
1✔
1183
            }
1184

1185
            // If none of the alternatives were found, fail immediately
1186
            if (!hasInputInstance) {
1✔
1187
                if (!simulate && !collectMissingIngredients) {
1✔
1188
                    // But first, re-insert all already-extracted instances
1189
                    for (T instance : inputInstances) {
1✔
1190
                        T remaining = storage.insert(instance, false);
1✔
1191
                        if (!matcher.isEmpty(remaining)) {
1✔
1192
                            throw new IllegalStateException(String.format("Extraction for a crafting recipe failed " +
×
1193
                                    "due to inconsistent insertion behaviour by destination in simulation " +
1194
                                    "and non-simulation: %s. Lost: %s", storage, remaining));
1195
                        }
1196
                    }
1✔
1197
                }
1198

1199
                if (!collectMissingIngredients) {
1✔
1200
                    // This input failed, return immediately
1201
                    return Pair.of(null, null);
1✔
1202
                } else {
1203
                    // Multiply missing collection if recipe quantity is higher than one
1204
                    if (missingAlternatives.size() > 0) {
1✔
1205
                        missingElements.add(new MissingIngredients.Element<>(missingAlternatives, recipe.isInputReusable(ingredientComponent, inputIndex)));
1✔
1206
                    }
1207
                }
1208
            }
1209

1210
            // Otherwise, append it to the list and carry on.
1211
            // If none of the instances were valid, we add the first partially valid instance.
1212
            if (hasInputInstance) {
1✔
1213
                inputInstances.add(inputInstance);
1✔
1214
            } else if (setFirstInputInstance && !matcher.isEmpty(firstInputInstance)) {
1✔
1215
                inputInstances.add(firstInputInstance);
1✔
1216
            }
1217
        }
1218

1219
        return Pair.of(
1✔
1220
                inputInstances,
1221
                collectMissingIngredients ? new MissingIngredients<>(missingElements) : null
1✔
1222
        );
1223
    }
1224

1225
    /**
1226
     * Get all required recipe input ingredients from the network.
1227
     *
1228
     * If multiple alternative inputs are possible,
1229
     * then only the first possible match will be taken.
1230
     *
1231
     * Note: Make sure that you first call in simulation-mode
1232
     * to see if the ingredients are available.
1233
     * If you immediately call this non-simulated,
1234
     * then there might be a chance that ingredients are lost
1235
     * from the network.
1236
     *
1237
     * @param network The target network.
1238
     * @param channel The target channel.
1239
     * @param recipe The recipe to get the inputs from.
1240
     * @param simulate If true, then the ingredients will effectively be removed from the network, not when false.
1241
     * @param recipeOutputQuantity The number of times the given recipe should be applied.
1242
     * @return The found ingredients or null.
1243
     */
1244
    @Nullable
1245
    public static IMixedIngredients getRecipeInputs(INetwork network, int channel,
1246
                                                    IRecipeDefinition recipe, boolean simulate,
1247
                                                    long recipeOutputQuantity) {
1248
        Map<IngredientComponent<?, ?>, List<?>> inputs = getRecipeInputs(getNetworkStorageGetter(network, channel, true),
×
1249
                recipe, simulate, Maps.newIdentityHashMap(), Maps.newIdentityHashMap(), false, recipeOutputQuantity).getLeft();
×
1250
        return inputs == null ? null : new MixedIngredients(inputs);
×
1251
    }
1252

1253
    /**
1254
     * Get all required recipe input ingredients from the crafting job's buffer.
1255
     *
1256
     * If multiple alternative inputs are possible,
1257
     * then only the first possible match will be taken.
1258
     *
1259
     * Note: Make sure that you first call in simulation-mode
1260
     * to see if the ingredients are available.
1261
     * If you immediately call this non-simulated,
1262
     * then there might be a chance that ingredients are lost
1263
     * from the buffer.
1264
     *
1265
     * @param craftingJob The crafting job.
1266
     * @param recipe The recipe to get the inputs from.
1267
     * @param simulate If true, then the ingredients will effectively be removed from the buffer, not when false.
1268
     * @param recipeOutputQuantity The number of times the given recipe should be applied.
1269
     * @return The found ingredients or null.
1270
     */
1271
    @Nullable
1272
    public static IMixedIngredients getRecipeInputsFromCraftingJobBuffer(CraftingJob craftingJob,
1273
                                                    IRecipeDefinition recipe, boolean simulate,
1274
                                                    long recipeOutputQuantity) {
1275
        Map<IngredientComponent<?, ?>, List<?>> inputs = getRecipeInputs(getCraftingJobBufferStorageGetter(craftingJob),
×
1276
                recipe, simulate, Maps.newIdentityHashMap(), Maps.newIdentityHashMap(), false, recipeOutputQuantity).getLeft();
×
1277
        return inputs == null ? null : new MixedIngredients(inputs);
×
1278
    }
1279

1280
    /**
1281
     * Create a callback function for getting a storage for an ingredient component from the given network channel.
1282
     * @param network The target network.
1283
     * @param channel The target channel.
1284
     * @param scheduleObservation If an observation inside the ingredients network should be scheduled.
1285
     * @return A callback function for getting a storage for an ingredient component.
1286
     */
1287
    public static Function<IngredientComponent<?, ?>, IIngredientComponentStorage> getNetworkStorageGetter(INetwork network, int channel, boolean scheduleObservation) {
1288
        return ingredientComponent -> getNetworkStorage(network, channel, ingredientComponent, scheduleObservation);
×
1289
    }
1290

1291
    /**
1292
     * Create a callback function for getting a storage for an ingredient component from the given crafting job buffer.
1293
     * @param craftingJob The crafting job.
1294
     * @return A callback function for getting a storage for an ingredient component.
1295
     */
1296
    public static Function<IngredientComponent<?, ?>, IIngredientComponentStorage> getCraftingJobBufferStorageGetter(CraftingJob craftingJob) {
1297
        return ingredientComponent -> {
×
1298
            List<?> list = craftingJob.getIngredientsStorageBuffer().getInstances(ingredientComponent);
×
1299
            return new IngredientComponentStorageSlottedCollectionWrapper<>(new IngredientList(ingredientComponent, list), Integer.MAX_VALUE, Integer.MAX_VALUE);
×
1300
        };
1301
    }
1302

1303
    /**
1304
     * Get all required recipe input ingredients based on a given storage callback.
1305
     *
1306
     * If multiple alternative inputs are possible,
1307
     * then only the first possible match will be taken.
1308
     *
1309
     * Note: Make sure that you first call in simulation-mode
1310
     * to see if the ingredients are available.
1311
     * If you immediately call this non-simulated,
1312
     * then there might be a chance that ingredients are lost
1313
     * from the network.
1314
     *
1315
     * @param storageGetter A callback function to get a storage for the given ingredient component.
1316
     * @param recipe The recipe to get the inputs from.
1317
     * @param simulate If true, then the ingredients will effectively be removed from the network, not when false.
1318
     * @param simulatedExtractionMemories This map remembers all extracted instances in simulation mode.
1319
     *                                    This is to make sure that instances can not be extracted multiple times
1320
     *                                    when simulating.
1321
     * @param extractionMemoriesReusable Like simulatedExtractionMemories, but it stores the reusable ingredients.
1322
     * @param collectMissingIngredients If missing ingredients should be collected.
1323
     *                                  If false, then the first returned mixed ingredients may be null
1324
     *                                  if no valid matches can be found,
1325
     *                                  and the second returned list is always null,
1326
     * @param recipeOutputQuantity The number of times the given recipe should be applied.
1327
     * @return A pair with two objects:
1328
     *           1. The found ingredients or null.
1329
     *           2. A mapping from ingredient component to missing ingredients (non-slot-based).
1330
     */
1331
    public static Pair<Map<IngredientComponent<?, ?>, List<?>>, Map<IngredientComponent<?, ?>, MissingIngredients<?, ?>>>
1332
    getRecipeInputs(Function<IngredientComponent<?, ?>, IIngredientComponentStorage> storageGetter, IRecipeDefinition recipe, boolean simulate,
1333
                    Map<IngredientComponent<?, ?>, IngredientCollectionPrototypeMap<?, ?>> simulatedExtractionMemories,
1334
                    Map<IngredientComponent<?, ?>, IIngredientCollectionMutable<?, ?>> extractionMemoriesReusable,
1335
                    boolean collectMissingIngredients, long recipeOutputQuantity) {
1336
        return getRecipeInputs(storageGetter, recipe, simulate, simulatedExtractionMemories, extractionMemoriesReusable,
1✔
1337
                collectMissingIngredients, recipeOutputQuantity, false);
1338
    }
1339

1340
    public static Pair<Map<IngredientComponent<?, ?>, List<?>>, Map<IngredientComponent<?, ?>, MissingIngredients<?, ?>>>
1341
    getRecipeInputs(Function<IngredientComponent<?, ?>, IIngredientComponentStorage> storageGetter, IRecipeDefinition recipe, boolean simulate,
1342
                    Map<IngredientComponent<?, ?>, IngredientCollectionPrototypeMap<?, ?>> simulatedExtractionMemories,
1343
                    Map<IngredientComponent<?, ?>, IIngredientCollectionMutable<?, ?>> extractionMemoriesReusable,
1344
                    boolean collectMissingIngredients, long recipeOutputQuantity, boolean skipReusableIngredients) {
1345
        // Determine available and missing ingredients
1346
        Map<IngredientComponent<?, ?>, List<?>> ingredientsAvailable = Maps.newIdentityHashMap();
1✔
1347
        Map<IngredientComponent<?, ?>, MissingIngredients<?, ?>> ingredientsMissing = Maps.newIdentityHashMap();
1✔
1348
        for (IngredientComponent<?, ?> ingredientComponent : recipe.getInputComponents()) {
1✔
1349
            IIngredientComponentStorage storage = storageGetter.apply(ingredientComponent);
1✔
1350
            IngredientCollectionPrototypeMap<?, ?> simulatedExtractionMemory = simulatedExtractionMemories.get(ingredientComponent);
1✔
1351
            if (simulatedExtractionMemory == null) {
1✔
1352
                simulatedExtractionMemory = new IngredientCollectionPrototypeMap<>(ingredientComponent, true);
1✔
1353
                simulatedExtractionMemories.put(ingredientComponent, simulatedExtractionMemory);
1✔
1354
            }
1355
            IIngredientCollectionMutable extractionMemoryReusable = extractionMemoriesReusable.get(ingredientComponent);
1✔
1356
            if (extractionMemoryReusable == null) {
1✔
1357
                extractionMemoryReusable = new IngredientHashSet<>(ingredientComponent);
1✔
1358
                extractionMemoriesReusable.put(ingredientComponent, extractionMemoryReusable);
1✔
1359
            }
1360
            Pair<List<?>, MissingIngredients<?, ?>> subIngredients = getIngredientRecipeInputs(storage,
1✔
1361
                    (IngredientComponent) ingredientComponent, recipe, simulate, simulatedExtractionMemory, extractionMemoryReusable,
1362
                    collectMissingIngredients, recipeOutputQuantity, skipReusableIngredients);
1363
            List<?> subIngredientAvailable = subIngredients.getLeft();
1✔
1364
            MissingIngredients<?, ?> subIngredientsMissing = subIngredients.getRight();
1✔
1365
            if (subIngredientAvailable == null && !collectMissingIngredients) {
1✔
1366
                return Pair.of(null, null);
×
1367
            } else {
1368
                if (subIngredientAvailable != null && !subIngredientAvailable.isEmpty()) {
1✔
1369
                    ingredientsAvailable.put(ingredientComponent, subIngredientAvailable);
1✔
1370
                }
1371
                if (collectMissingIngredients && !subIngredientsMissing.getElements().isEmpty()) {
1✔
1372
                    ingredientsMissing.put(ingredientComponent, subIngredientsMissing);
1✔
1373
                }
1374
            }
1375
        }
1✔
1376

1377
        // Compress missing ingredients
1378
        // We do this to ensure that instances missing multiple times can be easily combined
1379
        // when triggering a crafting job for them.
1380
        Map<IngredientComponent<?, ?>, MissingIngredients<?, ?>> ingredientsMissingCompressed = Maps.newIdentityHashMap();
1✔
1381
        for (IngredientComponent<?, ?> ingredientComponent : ingredientsMissing.keySet()) {
1✔
1382
            ingredientsMissingCompressed.put(ingredientComponent, compressMissingIngredients(ingredientsMissing.get(ingredientComponent)));
1✔
1383
        }
1✔
1384

1385
        return Pair.of(ingredientsAvailable, ingredientsMissingCompressed);
1✔
1386
    }
1387

1388
    /**
1389
     * Create a list of prototyped ingredients from the instances
1390
     * of the given ingredient component type in the given mixed ingredients.
1391
     *
1392
     * Equal prototypes will be stacked.
1393
     *
1394
     * @param ingredientComponent The ingredient component type.
1395
     * @param mixedIngredients The mixed ingredients.
1396
     * @param <T> The instance type.
1397
     * @param <M> The matching condition parameter.
1398
     * @return A list of prototypes.
1399
     */
1400
    public static <T, M> List<IPrototypedIngredient<T, M>> getCompressedIngredients(IngredientComponent<T, M> ingredientComponent,
1401
                                                                                    IMixedIngredients mixedIngredients) {
1402
        List<IPrototypedIngredient<T, M>> outputs = Lists.newArrayList();
1✔
1403

1404
        IIngredientMatcher<T, M> matcher = ingredientComponent.getMatcher();
1✔
1405
        for (T instance : mixedIngredients.getInstances(ingredientComponent)) {
1✔
1406
            // Try to stack this instance with an existing prototype
1407
            boolean stacked = false;
1✔
1408
            ListIterator<IPrototypedIngredient<T, M>> existingIt = outputs.listIterator();
1✔
1409
            while(existingIt.hasNext()) {
1✔
1410
                IPrototypedIngredient<T, M> prototypedIngredient = existingIt.next();
1✔
1411
                if (matcher.matches(instance, prototypedIngredient.getPrototype(),
1✔
1412
                        prototypedIngredient.getCondition())) {
1✔
1413
                    T stackedInstance = matcher.withQuantity(prototypedIngredient.getPrototype(),
1✔
1414
                            matcher.getQuantity(prototypedIngredient.getPrototype())
1✔
1415
                                    + matcher.getQuantity(instance));
1✔
1416
                    existingIt.set(new PrototypedIngredient<>(ingredientComponent, stackedInstance,
1✔
1417
                            prototypedIngredient.getCondition()));
1✔
1418
                    stacked = true;
1✔
1419
                    break;
1✔
1420
                }
1421
            }
1✔
1422

1423
            // If not possible, just append it to the list
1424
            if (!stacked) {
1✔
1425
                outputs.add(new PrototypedIngredient<>(ingredientComponent, instance,
1✔
1426
                        matcher.getExactMatchNoQuantityCondition()));
1✔
1427
            }
1428
        }
1✔
1429

1430
        return outputs;
1✔
1431
    }
1432

1433
    /**
1434
     * Compress the given missing ingredients so that equal instances just have an incremented quantity.
1435
     *
1436
     * @param missingIngredients The missing ingredients.
1437
     * @param <T> The instance type.
1438
     * @param <M> The matching condition parameter.
1439
     * @return A new missing ingredients object.
1440
     */
1441
    public static <T, M> MissingIngredients<T, M> compressMissingIngredients(MissingIngredients<T, M> missingIngredients) {
1442
        // Index identical missing ingredients in a map, to group them by quantity
1443
        Map<MissingIngredients.Element<T, M>, Long> elementsCompressedMap = Maps.newLinkedHashMap(); // Must be a linked map to maintain our order!!!
1✔
1444
        for (MissingIngredients.Element<T, M> element : missingIngredients.getElements()) {
1✔
1445
            elementsCompressedMap.merge(element, 1L, Long::sum);
1✔
1446
        }
1✔
1447

1448
        // Create a new missing ingredients list where we multiply the missing quantities
1449
        List<MissingIngredients.Element<T, M>> elementsCompressed = Lists.newArrayList();
1✔
1450
        for (Map.Entry<MissingIngredients.Element<T, M>, Long> entry : elementsCompressedMap.entrySet()) {
1✔
1451
            Long quantity = entry.getValue();
1✔
1452
            if (quantity == 1L || entry.getKey().isInputReusable()) {
1✔
1453
                elementsCompressed.add(entry.getKey());
1✔
1454
            } else {
1455
                MissingIngredients.Element<T, M> elementOld = entry.getKey();
1✔
1456
                MissingIngredients.Element<T, M> elementNewQuantity = new MissingIngredients.Element<>(
1✔
1457
                        elementOld.getAlternatives().stream()
1✔
1458
                                .map(alt -> new MissingIngredients.PrototypedWithRequested<>(alt.getRequestedPrototype(), alt.getQuantityMissing() * quantity))
1✔
1459
                                .toList(),
1✔
1460
                        elementOld.isInputReusable()
1✔
1461
                );
1462
                elementsCompressed.add(elementNewQuantity);
1✔
1463
            }
1464
        }
1✔
1465
        return new MissingIngredients<>(elementsCompressed);
1✔
1466
    }
1467

1468
    /**
1469
     * Create a collection of prototypes from the given recipe's outputs.
1470
     *
1471
     * Equal prototypes will be stacked.
1472
     *
1473
     * @param recipe A recipe.
1474
     * @return A map from ingredient component types to their list of prototypes.
1475
     */
1476
    public static Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>> getRecipeOutputs(IRecipeDefinition recipe) {
1477
        Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>> outputs = Maps.newHashMap();
1✔
1478

1479
        IMixedIngredients mixedIngredients = recipe.getOutput();
1✔
1480
        for (IngredientComponent ingredientComponent : mixedIngredients.getComponents()) {
1✔
1481
            outputs.put(ingredientComponent, getCompressedIngredients(ingredientComponent, mixedIngredients));
1✔
1482
        }
1✔
1483

1484
        return outputs;
1✔
1485
    }
1486

1487
    /**
1488
     * Creates a new recipe outputs object with all ingredient quantities multiplied by the given amount.
1489
     * @param recipeOutputs A recipe objects holder.
1490
     * @param amount An amount to multiply all instances by.
1491
     * @return A new recipe objects holder.
1492
     */
1493
    public static Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>> multiplyRecipeOutputs(
1494
            Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>> recipeOutputs, int amount) {
1495
        if (amount == 1) {
1✔
1496
            return recipeOutputs;
1✔
1497
        }
1498

1499
        Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>> newRecipeOutputs = Maps.newIdentityHashMap();
1✔
1500
        for (Map.Entry<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>> entry : recipeOutputs.entrySet()) {
1✔
1501
            newRecipeOutputs.put(entry.getKey(), multiplyPrototypedIngredients((List) entry.getValue(), amount));
1✔
1502
        }
1✔
1503
        return newRecipeOutputs;
1✔
1504
    }
1505

1506
    /**
1507
     * Multiply the quantity of a given prototyped ingredient list with the given amount.
1508
     * @param prototypedIngredients A prototyped ingredient list.
1509
     * @param amount An amount to multiply by.
1510
     * @param <T> The instance type.
1511
     * @param <M> The matching condition parameter.
1512
     * @return A multiplied prototyped ingredient list.
1513
     */
1514
    public static <T, M> List<IPrototypedIngredient<T, M>> multiplyPrototypedIngredients(Collection<IPrototypedIngredient<T, M>> prototypedIngredients,
1515
                                                                                         long amount) {
1516
        return prototypedIngredients
1✔
1517
                .stream()
1✔
1518
                .map(p -> multiplyPrototypedIngredient(p, amount))
1✔
1519
                .collect(Collectors.toList());
1✔
1520
    }
1521

1522
    /**
1523
     * Multiply the quantity of a given prototyped ingredient with the given amount.
1524
     * @param prototypedIngredient A prototyped ingredient.
1525
     * @param amount An amount to multiply by.
1526
     * @param <T> The instance type.
1527
     * @param <M> The matching condition parameter.
1528
     * @return A multiplied prototyped ingredient.
1529
     */
1530
    public static <T, M> IPrototypedIngredient<T, M> multiplyPrototypedIngredient(IPrototypedIngredient<T, M> prototypedIngredient,
1531
                                                                                  long amount) {
1532
        IIngredientMatcher<T, M> matcher = prototypedIngredient.getComponent().getMatcher();
1✔
1533
        return new PrototypedIngredient<>(prototypedIngredient.getComponent(),
1✔
1534
                matcher.withQuantity(prototypedIngredient.getPrototype(),
1✔
1535
                        matcher.getQuantity(prototypedIngredient.getPrototype()) * amount),
1✔
1536
                prototypedIngredient.getCondition());
1✔
1537
    }
1538

1539
    /**
1540
     * Merge two mixed ingredients in a new mixed ingredients object.
1541
     * Instances will be stacked.
1542
     * @param a A first mixed ingredients object.
1543
     * @param b A second mixed ingredients object.
1544
     * @return A merged mixed ingredients object.
1545
     */
1546
    public static IMixedIngredients mergeMixedIngredients(IMixedIngredients a, IMixedIngredients b) {
1547
        // Temporarily store instances in IngredientCollectionPrototypeMaps
1548
        Map<IngredientComponent<?, ?>, IngredientCollectionQuantitativeGrouper<?, ?, IngredientArrayList<?, ?>>> groupings = Maps.newIdentityHashMap();
1✔
1549
        for (IngredientComponent<?, ?> component : a.getComponents()) {
1✔
1550
            IngredientCollectionQuantitativeGrouper grouping = new IngredientCollectionQuantitativeGrouper<>(new IngredientArrayList<>(component));
1✔
1551
            groupings.put(component, grouping);
1✔
1552
            grouping.addAll(a.getInstances(component));
1✔
1553
        }
1✔
1554
        for (IngredientComponent<?, ?> component : b.getComponents()) {
1✔
1555
            IngredientCollectionQuantitativeGrouper grouping = groupings.get(component);
1✔
1556
            if (grouping == null) {
1✔
1557
                grouping = new IngredientCollectionQuantitativeGrouper<>(new IngredientArrayList<>(component));
1✔
1558
                groupings.put(component, grouping);
1✔
1559
            }
1560
            grouping.addAll(b.getInstances(component));
1✔
1561
        }
1✔
1562

1563
        // Convert IngredientCollectionPrototypeMaps to lists
1564
        Map<IngredientComponent<?, ?>, List<?>> ingredients = Maps.newIdentityHashMap();
1✔
1565
        for (Map.Entry<IngredientComponent<?, ?>, IngredientCollectionQuantitativeGrouper<?, ?, IngredientArrayList<?, ?>>> entry : groupings.entrySet()) {
1✔
1566
            ingredients.put(entry.getKey(), Lists.newArrayList(entry.getValue()));
1✔
1567
        }
1✔
1568
        return new MixedIngredients(ingredients);
1✔
1569
    }
1570

1571
    /**
1572
     * Stack all ingredients in the given mixed ingredients object.
1573
     * @param mixedIngredients A mixed ingredients object.
1574
     * @return A new mixed ingredients object.
1575
     */
1576
    protected static IMixedIngredients compressMixedIngredients(IMixedIngredients mixedIngredients) {
1577
        // Temporarily store instances in IngredientCollectionPrototypeMaps
1578
        Map<IngredientComponent<?, ?>, IngredientCollectionQuantitativeGrouper<?, ?, IngredientArrayList<?, ?>>> groupings = Maps.newIdentityHashMap();
1✔
1579
        for (IngredientComponent<?, ?> component : mixedIngredients.getComponents()) {
1✔
1580
            IngredientCollectionQuantitativeGrouper grouping = new IngredientCollectionQuantitativeGrouper<>(new IngredientArrayList<>(component));
1✔
1581
            groupings.put(component, grouping);
1✔
1582
            grouping.addAll(mixedIngredients.getInstances(component));
1✔
1583
        }
1✔
1584

1585
        // Convert IngredientCollectionPrototypeMaps to lists
1586
        Map<IngredientComponent<?, ?>, List<?>> ingredients = Maps.newIdentityHashMap();
1✔
1587
        for (Map.Entry<IngredientComponent<?, ?>, IngredientCollectionQuantitativeGrouper<?, ?, IngredientArrayList<?, ?>>> entry : groupings.entrySet()) {
1✔
1588
            IIngredientMatcher matcher = entry.getKey().getMatcher();
1✔
1589
            List<?> values = entry.getValue()
1✔
1590
                    .stream()
1✔
1591
                    .filter(i -> !matcher.isEmpty(i))
1✔
1592
                    .collect(Collectors.toList());
1✔
1593
            if (!values.isEmpty()) {
1✔
1594
                ingredients.put(entry.getKey(), values);
1✔
1595
            }
1596
        }
1✔
1597
        return new MixedIngredients(ingredients);
1✔
1598
    }
1599

1600
    /**
1601
     * Insert the ingredients of the given ingredient component type into the target to make it start crafting.
1602
     *
1603
     * If insertion in non-simulation mode fails,
1604
     * ingredients will be re-inserted into the network.
1605
     *
1606
     * @param ingredientComponent The ingredient component type.
1607
     * @param capabilityProvider The target capability provider.
1608
     * @param side The target side.
1609
     * @param ingredients The ingredients to insert.
1610
     * @param storageFallback The storage to insert any failed ingredients back into. Can be null in simulation mode.
1611
     * @param simulate If insertion should be simulated.
1612
     * @param <T> The instance type.
1613
     * @param <M> The matching condition parameter.
1614
     * @return If all instances could be inserted.
1615
     */
1616
    public static <T, M> boolean insertIngredientCrafting(IngredientComponent<T, M> ingredientComponent,
1617
                                                          ICapabilityProvider capabilityProvider,
1618
                                                          @Nullable Direction side,
1619
                                                          IMixedIngredients ingredients,
1620
                                                          IIngredientComponentStorage<T, M> storageFallback,
1621
                                                          boolean simulate) {
1622
        IIngredientMatcher<T, M> matcher = ingredientComponent.getMatcher();
×
1623
        IIngredientComponentStorage<T, M> storage = ingredientComponent.getStorage(capabilityProvider, side);
×
1624
        List<T> instances = ingredients.getInstances(ingredientComponent);
×
1625
        List<T> failedInstances = Lists.newArrayList();
×
1626
        boolean ok = true;
×
1627
        for (T instance : instances) {
×
1628
            T remaining = storage.insert(instance, simulate);
×
1629
            if (!matcher.isEmpty(remaining)) {
×
1630
                ok = false;
×
1631
                if (!simulate) {
×
1632
                    failedInstances.add(remaining);
×
1633
                }
1634
            }
1635
        }
×
1636

1637
        // If we had failed insertions, try to insert them back into the network.
1638
        for (T instance : failedInstances) {
×
1639
            T remaining = storageFallback.insert(instance, false);
×
1640
            if (!matcher.isEmpty(remaining)) {
×
1641
                throw new IllegalStateException(String.format("Insertion for a crafting recipe failed " +
×
1642
                        "due to inconsistent insertion behaviour by destination in simulation " +
1643
                        "and non-simulation: %s. Lost: %s", capabilityProvider, instances));
1644
            }
1645
        }
×
1646

1647
        return ok;
×
1648
    }
1649

1650
    /**
1651
     * Insert the ingredients of all applicable ingredient component types into the target to make it start crafting.
1652
     *
1653
     * If insertion in non-simulation mode fails,
1654
     * ingredients will be re-inserted into the network.
1655
     *
1656
     * @param targetGetter A function to get the target position.
1657
     * @param ingredients The ingredients to insert.
1658
     * @param network The network.
1659
     * @param channel The channel.
1660
     * @param simulate If insertion should be simulated.
1661
     * @return If all instances could be inserted.
1662
     */
1663
    public static boolean insertCrafting(Function<IngredientComponent<?, ?>, PartPos> targetGetter,
1664
                                         IMixedIngredients ingredients,
1665
                                         INetwork network, int channel,
1666
                                         boolean simulate) {
1667
        Map<IngredientComponent<?, ?>, BlockEntity> tileMap = Maps.newIdentityHashMap();
×
1668

1669
        // First, check if we can find valid tiles for all ingredient components
1670
        for (IngredientComponent<?, ?> ingredientComponent : ingredients.getComponents()) {
×
1671
            BlockEntity tile = BlockEntityHelpers.get(targetGetter.apply(ingredientComponent).getPos(), BlockEntity.class).orElse(null);
×
1672
            if (tile != null) {
×
1673
                tileMap.put(ingredientComponent, tile);
×
1674
            } else {
1675
                return false;
×
1676
            }
1677
        }
×
1678

1679
        // Next, insert the instances into the respective tiles
1680
        boolean ok = true;
×
1681
        for (Map.Entry<IngredientComponent<?, ?>, BlockEntity> entry : tileMap.entrySet()) {
×
1682
            IIngredientComponentStorage<?, ?> storageNetwork = simulate ? null : getNetworkStorage(network, channel, entry.getKey(), false);
×
1683
            if (!insertIngredientCrafting((IngredientComponent) entry.getKey(), entry.getValue(),
×
1684
                    targetGetter.apply(entry.getKey()).getSide(), ingredients,
×
1685
                    storageNetwork, simulate)) {
1686
                ok = false;
×
1687
            }
1688
        }
×
1689

1690
        return ok;
×
1691
    }
1692

1693
    /**
1694
     * Split the given crafting job amount into new jobs with a given split factor.
1695
     * @param craftingJob A crafting job to split.
1696
     * @param splitFactor The number of jobs to split the job over.
1697
     * @param dependencyGraph The dependency graph that will be updated if there are dependencies in the original job.
1698
     * @param identifierGenerator An identifier generator for the crafting jobs ids.
1699
     * @return The newly created crafting jobs.
1700
     */
1701
    public static List<CraftingJob> splitCraftingJobs(CraftingJob craftingJob, int splitFactor,
1702
                                                      CraftingJobDependencyGraph dependencyGraph,
1703
                                                      IIdentifierGenerator identifierGenerator) {
1704
        splitFactor = Math.min(splitFactor, craftingJob.getAmount());
1✔
1705
        int division = craftingJob.getAmount() / splitFactor;
1✔
1706
        int modulus = craftingJob.getAmount() % splitFactor;
1✔
1707

1708
        // Clone original job into splitFactor jobs
1709
        List<CraftingJob> newCraftingJobs = Lists.newArrayList();
1✔
1710
        for (int i = 0; i < splitFactor; i++) {
1✔
1711
            CraftingJob clonedJob = craftingJob.clone(identifierGenerator);
1✔
1712
            newCraftingJobs.add(clonedJob);
1✔
1713

1714
            // Update amount
1715
            int newAmount = division;
1✔
1716
            if (modulus > 0) {
1✔
1717
                // No amounts will be lost, as modulus is guaranteed to be smaller than splitFactor
1718
                newAmount++;
1✔
1719
                modulus--;
1✔
1720
            }
1721
            clonedJob.setAmount(newAmount);
1✔
1722
        }
1723

1724
        // Collect dependency links
1725
        Collection<CraftingJob> originalDependencies = dependencyGraph.getDependencies(craftingJob);
1✔
1726
        Collection<CraftingJob> originalDependents = dependencyGraph.getDependents(craftingJob);
1✔
1727

1728
        // Remove dependency links to and from the original jobs
1729
        for (CraftingJob dependency : originalDependencies) {
1✔
1730
            craftingJob.removeDependency(dependency);
1✔
1731
            dependencyGraph.removeDependency(craftingJob.getId(), dependency.getId());
1✔
1732
        }
1✔
1733
        for (CraftingJob dependent : originalDependents) {
1✔
1734
            dependent.removeDependency(craftingJob);
1✔
1735
            dependencyGraph.removeDependency(dependent.getId(), craftingJob.getId());
1✔
1736
        }
1✔
1737

1738
        // Create dependency links to and from the new crafting jobs
1739
        for (CraftingJob dependency : originalDependencies) {
1✔
1740
            for (CraftingJob newCraftingJob : newCraftingJobs) {
1✔
1741
                newCraftingJob.addDependency(dependency);
1✔
1742
                dependencyGraph.addDependency(newCraftingJob, dependency);
1✔
1743
            }
1✔
1744
        }
1✔
1745
        for (CraftingJob originalDependent : originalDependents) {
1✔
1746
            for (CraftingJob newCraftingJob : newCraftingJobs) {
1✔
1747
                originalDependent.addDependency(newCraftingJob);
1✔
1748
                dependencyGraph.addDependency(originalDependent, newCraftingJob);
1✔
1749
            }
1✔
1750
        }
1✔
1751

1752
        return newCraftingJobs;
1✔
1753
    }
1754

1755
    /**
1756
     * Insert the given ingredients into the given storage networks.
1757
     * @param ingredients A collection of ingredients.
1758
     * @param storageGetter A storage network getter.
1759
     * @param simulate If insertion should be simulated.
1760
     * @return The remaining ingredients that were not inserted.
1761
     */
1762
    public static IMixedIngredients insertIngredients(IMixedIngredients ingredients,
1763
                                                      Function<IngredientComponent<?, ?>, IIngredientComponentStorage> storageGetter,
1764
                                                      boolean simulate) {
1765
        Map<IngredientComponent<?, ?>, List<?>> remainingIngredients = Maps.newIdentityHashMap();
×
1766
        for (IngredientComponent<?, ?> component : ingredients.getComponents()) {
×
1767
            IIngredientComponentStorage storage = storageGetter.apply(component);
×
1768
            IIngredientMatcher matcher = component.getMatcher();
×
1769
            for (Object instance : ingredients.getInstances(component)) {
×
1770
                Object remainder = storage.insert(instance, simulate);
×
1771
                if (!matcher.isEmpty(remainder)) {
×
1772
                    List remainingInstances = remainingIngredients.get(component);
×
1773
                    if (remainingInstances == null) {
×
1774
                        remainingInstances = Lists.newArrayList();
×
1775
                        remainingIngredients.put(component, remainingInstances);
×
1776
                    }
1777
                    remainingInstances.add(instance);
×
1778
                }
1779
            }
×
1780
        }
×
1781
        return new MixedIngredients(remainingIngredients);
×
1782
    }
1783

1784
    /**
1785
     * Insert the given ingredients into the given storage networks.
1786
     * If something fails to be inserted, produce a warning.
1787
     * @param ingredients A collection of ingredients.
1788
     * @param storageGetter A storage network getter.
1789
     * @param failureSink Ingredients that failed to be inserted will be inserted to this sink.
1790
     */
1791
    public static void insertIngredientsGuaranteed(IMixedIngredients ingredients,
1792
                                                   Function<IngredientComponent<?, ?>, IIngredientComponentStorage> storageGetter,
1793
                                                   ICraftingResultsSink failureSink) {
1794
        IMixedIngredients remaining = insertIngredients(ingredients, storageGetter, false);
×
1795
        // If re-insertion into the network fails, insert it into the buffer of the crafting interface,
1796
        // to avoid loss of ingredients.
1797
        if (!remaining.isEmpty()) {
×
1798
            for (IngredientComponent<?, ?> component : remaining.getComponents()) {
×
1799
                for (Object instance : remaining.getInstances(component)) {
×
1800
                    failureSink.addResult((IngredientComponent) component, instance);
×
1801
                }
×
1802
            }
×
1803
        }
1804
    }
×
1805

1806
    /**
1807
     * Generates semi-unique IDs.
1808
     */
1809
    public static interface IIdentifierGenerator {
1810
        public int getNext();
1811
    }
1812

1813
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc