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

CyclopsMC / IntegratedCrafting / #479011800

31 Aug 2025 01:00PM UTC coverage: 24.876% (-0.3%) from 25.162%
#479011800

push

github

rubensworks
Add Attuned Crafting Interface

1 of 277 new or added lines in 7 files covered. (0.36%)

113 existing lines in 4 files now uncovered.

750 of 3015 relevant lines covered (24.88%)

0.25 hits per line

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

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

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

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

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

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

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

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

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

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

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

139
    /**
140
     * If the network is guaranteed to have uncommitted changes (such as the one in #48),
141
     * forcefully run observers synchronously, so that we can calculate the job in a consistent network state.
142
     * @param network The network.
143
     * @param channel A network channel.
144
     */
145
    public static void beforeCalculateCraftingJobs(INetwork network, int channel) {
UNCOV
146
        for (IngredientComponent<?, ?> ingredientComponent : IngredientComponent.REGISTRY.getValues()) {
×
UNCOV
147
            IPositionedAddonsNetworkIngredients<?, ?> ingredientsNetwork = getIngredientsNetwork(network, ingredientComponent).orElse(null);
×
UNCOV
148
            if (ingredientsNetwork != null && (ingredientsNetwork.isObservationForcedPending(channel))) {
×
UNCOV
149
                ingredientsNetwork.runObserverSync();
×
150
            }
151
        }
×
152
    }
×
153

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

UNCOV
185
        CraftingJob craftingJob = calculateCraftingJobs(recipeIndex, channel, storageGetter, ingredientComponent, instance, matchCondition,
×
UNCOV
186
                craftMissing, Maps.newIdentityHashMap(), Maps.newIdentityHashMap(), identifierGenerator, craftingJobsGraph, Sets.newHashSet(),
×
187
                collectMissingRecipes);
UNCOV
188
        craftingJobsGraph.addCraftingJobId(craftingJob);
×
UNCOV
189
        return craftingJob;
×
190
    }
191

192
    /**
193
     * Calculate the required crafting jobs and their dependencies for the given instance in the given network.
194
     * @param network The target network.
195
     * @param channel The target channel.
196
     * @param recipe The recipe to calculate a job for.
197
     * @param amount The amount of times the recipe should be crafted.
198
     * @param craftMissing If the missing required ingredients should also be crafted.
199
     * @param identifierGenerator identifierGenerator An ID generator for crafting jobs.
200
     * @param craftingJobsGraph The target graph where all dependencies will be stored.
201
     * @param collectMissingRecipes If the missing recipes should be collected inside
202
     *                              {@link FailedCraftingRecipeException}.
203
     *                              This may slow down calculation for deeply nested recipe graphs.
204
     * @return The crafting job for the given instance.
205
     * @throws FailedCraftingRecipeException If the recipe could not be crafted due to missing sub-dependencies.
206
     * @throws RecursiveCraftingRecipeException If an infinite recursive recipe was detected.
207
     */
208
    public static CraftingJob calculateCraftingJobs(INetwork network, int channel,
209
                                                    IRecipeDefinition recipe, int amount, boolean craftMissing,
210
                                                    IIdentifierGenerator identifierGenerator,
211
                                                    CraftingJobDependencyGraph craftingJobsGraph,
212
                                                    boolean collectMissingRecipes)
213
            throws FailedCraftingRecipeException, RecursiveCraftingRecipeException {
UNCOV
214
        ICraftingNetwork craftingNetwork = getCraftingNetworkChecked(network);
×
UNCOV
215
        IRecipeIndex recipeIndex = craftingNetwork.getRecipeIndex(channel);
×
UNCOV
216
        Function<IngredientComponent<?, ?>, IIngredientComponentStorage> storageGetter = getNetworkStorageGetter(network, channel, true);
×
UNCOV
217
        beforeCalculateCraftingJobs(network, channel);
×
218

UNCOV
219
        PartialCraftingJobCalculation result = calculateCraftingJobs(recipeIndex, channel, storageGetter, recipe, amount,
×
UNCOV
220
                craftMissing, Maps.newIdentityHashMap(), Maps.newIdentityHashMap(), identifierGenerator, craftingJobsGraph, Sets.newHashSet(),
×
221
                collectMissingRecipes);
UNCOV
222
        if (result.getCraftingJob() == null) {
×
UNCOV
223
            throw new FailedCraftingRecipeException(recipe, amount, result.getMissingDependencies(),
×
UNCOV
224
                    compressMixedIngredients(new MixedIngredients(result.getIngredientsStorage())), result.getPartialCraftingJobs());
×
225
        } else {
UNCOV
226
            craftingJobsGraph.addCraftingJobId(result.getCraftingJob());
×
UNCOV
227
            return result.getCraftingJob();
×
228
        }
229
    }
230

231
    /**
232
     * @return An identifier generator for crafting jobs.
233
     */
234
    public static IIdentifierGenerator getGlobalCraftingJobIdentifier() {
235
        return () -> IntegratedCrafting.globalCounters.getNext("craftingJob");
×
236
    }
237

238
    /**
239
     * Calculate the effective quantity for the given instance in the output of the given recipe.
240
     * @param recipe A recipe.
241
     * @param ingredientComponent The ingredient component.
242
     * @param instance An instance.
243
     * @param matchCondition A match condition.
244
     * @param <T> The instance type.
245
     * @param <M> The matching condition parameter.
246
     * @return The effective quantity.
247
     */
248
    public static <T, M> long getOutputQuantityForRecipe(IRecipeDefinition recipe,
249
                                                         IngredientComponent<T, M> ingredientComponent,
250
                                                         T instance, M matchCondition) {
251
        IIngredientMatcher<T, M> matcher = ingredientComponent.getMatcher();
1✔
252
        return recipe.getOutput().getInstances(ingredientComponent)
1✔
253
                .stream()
1✔
254
                .filter(i -> matcher.matches(i, instance, matchCondition))
1✔
255
                .mapToLong(matcher::getQuantity)
1✔
256
                .sum();
1✔
257
    }
258

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

308
        // Loop over all available recipes, and return the first valid one.
309
        Iterator<IRecipeDefinition> recipes = recipeIndex.getRecipes(ingredientComponent, instance, quantifierlessCondition);
1✔
310
        List<UnknownCraftingRecipeException> firstMissingDependencies = Lists.newArrayList();
1✔
311
        Map<IngredientComponent<?, ?>, List<?>> firstIngredientsStorage = Collections.emptyMap();
1✔
312
        List<CraftingJob> firstPartialCraftingJobs = Lists.newArrayList();
1✔
313
        RecursiveCraftingRecipeException firstRecursiveException = null;
1✔
314
        while (recipes.hasNext()) {
1✔
315
            IRecipeDefinition recipe = recipes.next();
1✔
316

317
            // Calculate the quantity for the given instance that the recipe outputs
318
            long recipeOutputQuantity = getOutputQuantityForRecipe(recipe, ingredientComponent, instance, quantifierlessCondition);
1✔
319
            // Based on the quantity of the recipe output, calculate the amount of required recipe jobs.
320
            int amount = (int) Math.ceil(((float) instanceQuantity) / (float) recipeOutputQuantity);
1✔
321

322
            // Calculate jobs for the given recipe
323
            try {
324
                PartialCraftingJobCalculation result = calculateCraftingJobs(recipeIndex, channel,
1✔
325
                        storageGetter, recipe, amount, craftMissing,
326
                        simulatedExtractionMemory, extractionMemoryReusable, identifierGenerator, craftingJobsGraph, parentDependencies,
327
                        collectMissingRecipes && firstMissingDependencies.isEmpty());
1✔
328
                if (result.getCraftingJob() == null) {
1✔
329
                    firstMissingDependencies = result.getMissingDependencies();
1✔
330
                    firstIngredientsStorage = result.getIngredientsStorage();
1✔
331
                    if (result.getPartialCraftingJobs() != null) {
1✔
332
                        firstPartialCraftingJobs = result.getPartialCraftingJobs();
1✔
333
                    }
334
                } else {
335
                    return result.getCraftingJob();
1✔
336
                }
337
            } catch (RecursiveCraftingRecipeException e) {
1✔
338
                if (firstRecursiveException == null) {
1✔
339
                    firstRecursiveException = e;
1✔
340
                }
341
                continue;
1✔
342
            }
1✔
343
        }
1✔
344

345
        if (firstRecursiveException != null) {
1✔
346
            throw firstRecursiveException;
1✔
347
        }
348

349
        // No valid recipes were available, so we error or collect the missing instance.
350
        throw new UnknownCraftingRecipeException(new PrototypedIngredient<>(ingredientComponent, instance, matchCondition),
1✔
351
                matcher.getQuantity(instance), firstMissingDependencies, compressMixedIngredients(new MixedIngredients(firstIngredientsStorage)),
1✔
352
                firstPartialCraftingJobs);
353
    }
354

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

397
        // Check if all requirements are met for this recipe, if so return directly (don't schedule yet)
398
        Pair<Map<IngredientComponent<?, ?>, List<?>>, Map<IngredientComponent<?, ?>, MissingIngredients<?, ?>>> simulation =
1✔
399
                getRecipeInputs(storageGetter, recipe, true, simulatedExtractionMemory, extractionMemoryReusable,
1✔
400
                        true, amount);
401
        Map<IngredientComponent<?, ?>, MissingIngredients<?, ?>> missingIngredients = simulation.getRight();
1✔
402
        if (!craftMissing && !missingIngredients.isEmpty()) {
1✔
403
            if (collectMissingRecipes) {
1✔
404
                // Collect missing ingredients as missing recipes when we don't want to craft sub-components,
405
                // but they are missing.
406
                for (Map.Entry<IngredientComponent<?, ?>, MissingIngredients<?, ?>> entry : missingIngredients.entrySet()) {
1✔
407
                    for (MissingIngredients.Element<?, ?> element : entry.getValue().getElements()) {
1✔
408
                        MissingIngredients.PrototypedWithRequested<?, ?> alternative = element.getAlternatives().get(0);
1✔
409

410
                        // Calculate the instance that was available in storage
411
                        IngredientComponent<?, ?> component = alternative.getRequestedPrototype().getComponent();
1✔
412
                        IIngredientMatcher matcher = component.getMatcher();
1✔
413
                        long storedQuantity = matcher.getQuantity(alternative.getRequestedPrototype().getPrototype()) - alternative.getQuantityMissing();
1✔
414
                        Map<IngredientComponent<?, ?>, List<?>> storageMap;
415
                        if (storedQuantity > 0) {
1✔
UNCOV
416
                            storageMap = Maps.newIdentityHashMap();
×
UNCOV
417
                            storageMap.put(component, Collections.singletonList(
×
UNCOV
418
                                    matcher.withQuantity(alternative.getRequestedPrototype().getPrototype(), storedQuantity)
×
419
                            ));
420
                        } else {
421
                            storageMap = Collections.emptyMap();
1✔
422
                        }
423

424
                        missingDependencies.add(new UnknownCraftingRecipeException(
1✔
425
                                alternative.getRequestedPrototype(), alternative.getQuantityMissing(),
1✔
426
                                Collections.emptyList(), compressMixedIngredients(new MixedIngredients(storageMap)), Lists.newArrayList()));
1✔
427
                    }
1✔
428
                }
1✔
429
            }
430
            return new PartialCraftingJobCalculation(null, missingDependencies, simulation.getLeft(), null);
1✔
431
        }
432

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

475
        // If at least one of our dependencies does not have a valid recipe or is not available,
476
        // go check the next recipe.
477
        if (!missingDependencies.isEmpty()) {
1✔
478
            return new PartialCraftingJobCalculation(null, missingDependencies, simulation.getLeft(), partialCraftingJobs);
1✔
479
        }
480

481
        CraftingJob craftingJob = new CraftingJob(identifierGenerator.getNext(), channel, recipe, amount,
1✔
482
                compressMixedIngredients(new MixedIngredients(simulation.getLeft())));
1✔
483
        for (CraftingJob dependency : dependencies.values()) {
1✔
484
            craftingJob.addDependency(dependency);
1✔
485
            craftingJobsGraph.addDependency(craftingJob, dependency);
1✔
486
        }
1✔
487
        return new PartialCraftingJobCalculation(craftingJob, null, simulation.getLeft(), null);
1✔
488
    }
489

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

525
                IPrototypedIngredient<T, M> prototype = new PrototypedIngredient<>(
1✔
526
                        dependencyComponent,
527
                        dependencyMatcher.withQuantity(prototypedAlternative.getRequestedPrototype().getPrototype(), prototypedAlternative.getQuantityMissing()),
1✔
528
                        prototypedAlternative.getRequestedPrototype().getCondition()
1✔
529
                );
530
                // First check if we can grab it from previous surplus
531
                IngredientCollectionPrototypeMap<T, M> dependencyComponentSurplusOld = (IngredientCollectionPrototypeMap<T, M>) dependenciesOutputSurplus.get(dependencyComponent);
1✔
532
                IngredientCollectionPrototypeMap<T, M> dependencyComponentSurplus = null;
1✔
533
                if (dependencyComponentSurplusOld != null) {
1✔
534
                    // First create a copy of the given surplus store,
535
                    // and only once we see that the prototype is valid,
536
                    // save this copy again.
537
                    // This is because if this prototype is invalid,
538
                    // then we don't want these invalid surpluses.
539
                    dependencyComponentSurplus = new IngredientCollectionPrototypeMap<>(dependencyComponentSurplusOld.getComponent(), true);
1✔
540
                    dependencyComponentSurplus.addAll(dependencyComponentSurplusOld);
1✔
541

542
                    long remainingQuantity = dependencyMatcher.getQuantity(prototype.getPrototype());
1✔
543
                    IIngredientMatcher<T, M> prototypeMatcher = prototype.getComponent().getMatcher();
1✔
544
                    // Check all instances in the surplus that match with the given prototype
545
                    // For each match, we subtract its quantity from the required quantity.
546
                    Iterator<T> surplusIt = dependencyComponentSurplus.iterator(prototype.getPrototype(),
1✔
547
                            prototypeMatcher.withoutCondition(prototype.getCondition(), prototype.getComponent().getPrimaryQuantifier().getMatchCondition()));
1✔
548
                    boolean updatedRemainingQuantity = false;
1✔
549
                    while (remainingQuantity > 0 && surplusIt.hasNext()) {
1✔
550
                        updatedRemainingQuantity = true;
1✔
551
                        T matchingInstance = surplusIt.next();
1✔
552
                        long matchingInstanceQuantity = dependencyMatcher.getQuantity(matchingInstance);
1✔
553
                        if (matchingInstanceQuantity <= remainingQuantity) {
1✔
554
                            // This whole surplus instance can be consumed
555
                            remainingQuantity -= matchingInstanceQuantity;
1✔
556
                            surplusIt.remove();
1✔
557
                        } else {
558
                            // Only part of this surplus instance can be consumed.
UNCOV
559
                            matchingInstanceQuantity -= remainingQuantity;
×
UNCOV
560
                            remainingQuantity = 0;
×
UNCOV
561
                            surplusIt.remove();
×
UNCOV
562
                            dependencyComponentSurplus.setQuantity(matchingInstance, matchingInstanceQuantity);
×
563
                        }
564
                    }
1✔
565
                    if (updatedRemainingQuantity) {
1✔
566
                        if (remainingQuantity == 0) {
1✔
567
                            // The prototype is valid,
568
                            // so we can finally store our temporary surplus
569
                            dependenciesOutputSurplus.put(dependencyComponent, dependencyComponentSurplus);
×
570

571
                            // Nothing has to be crafted anymore, jump to next dependency
UNCOV
572
                            skipDependency = true;
×
UNCOV
573
                            break;
×
574
                        } else {
575
                            // Partial availability, other part needs to be crafted still.
576
                            prototype = new PrototypedIngredient<>(dependencyComponent,
1✔
577
                                    dependencyMatcher.withQuantity(prototype.getPrototype(), remainingQuantity),
1✔
578
                                    prototype.getCondition());
1✔
579
                        }
580
                    }
581
                }
582

583
                // Try to craft the given prototype
584
                try {
585
                    Set<IPrototypedIngredient> childDependencies = Sets.newHashSet(parentDependencies);
1✔
586
                    IPrototypedIngredient<T, M> dependencyPrototype = prototype;
1✔
587
                    if (dependencyMatcher.getQuantity(dependencyPrototype.getPrototype()) != 1) {
1✔
588
                        // Ensure 1-quantity is stored, for proper comparisons in future calls.
589
                        dependencyPrototype = new PrototypedIngredient<>(dependencyComponent,
1✔
590
                                dependencyMatcher.withQuantity(prototype.getPrototype(), 1),
1✔
591
                                prototype.getCondition());
1✔
592
                    }
593
                    if (!childDependencies.add(dependencyPrototype)) {
1✔
594
                        throw new RecursiveCraftingRecipeException(prototype);
1✔
595
                    }
596

597
                    dependency = calculateCraftingJobs(recipeIndex, channel, storageGetter,
1✔
598
                            dependencyComponent, prototype.getPrototype(),
1✔
599
                            prototype.getCondition(), true, simulatedExtractionMemory, extractionMemoryReusable,
1✔
600
                            identifierGenerator, craftingJobsGraph, childDependencies, collectMissingRecipes);
601
                    dependencyInstance = prototype.getPrototype();
1✔
602

603
                    // The prototype is valid,
604
                    // so we can finally store our temporary surplus
605
                    if (dependencyComponentSurplus != null) {
1✔
606
                        dependenciesOutputSurplus.put(dependencyComponent, dependencyComponentSurplus);
1✔
607
                    }
608

609
                    // Add the auxiliary recipe outputs that are not requested to the surplus
610
                    Object dependencyQuantifierlessCondition = dependencyMatcher.withoutCondition(prototype.getCondition(),
1✔
611
                            dependencyComponent.getPrimaryQuantifier().getMatchCondition());
1✔
612
                    long requestedQuantity = dependencyMatcher.getQuantity(prototype.getPrototype());
1✔
613
                    for (IngredientComponent outputComponent : dependency.getRecipe().getOutput().getComponents()) {
1✔
614
                        IngredientCollectionPrototypeMap<?, ?> componentSurplus = dependenciesOutputSurplus.get(outputComponent);
1✔
615
                        if (componentSurplus == null) {
1✔
616
                            componentSurplus = new IngredientCollectionPrototypeMap<>(outputComponent, true);
1✔
617
                            dependenciesOutputSurplus.put(outputComponent, componentSurplus);
1✔
618
                        }
619
                        List<Object> instances = dependency.getRecipe().getOutput().getInstances(outputComponent);
1✔
620
                        long recipeAmount = dependency.getAmount();
1✔
621
                        if (recipeAmount > 1) {
1✔
622
                            IIngredientMatcher matcher = outputComponent.getMatcher();
1✔
623
                            // If more than one recipe amount was crafted, correctly multiply the outputs to calculate the effective surplus.
624
                            instances = instances
1✔
625
                                    .stream()
1✔
626
                                    .map(instance -> matcher.withQuantity(instance, matcher.getQuantity(instance) * recipeAmount))
1✔
627
                                    .collect(Collectors.toList());
1✔
628
                        }
629
                        addRemainderAsSurplusForComponent(outputComponent, (List) instances, componentSurplus,
1✔
630
                                (IngredientComponent) prototype.getComponent(), prototype.getPrototype(), dependencyQuantifierlessCondition,
1✔
631
                                requestedQuantity);
632
                    }
1✔
633

634
                    break;
1✔
635
                } catch (UnknownCraftingRecipeException e) {
1✔
636
                    // Save the first error, and check the next prototype
637
                    if (firstError == null) {
1✔
638
                        // Modify the error so that the correct missing quantity is stored
639
                        firstError = new UnknownCraftingRecipeException(
1✔
640
                                e.getIngredient(), prototypedAlternative.getQuantityMissing(),
1✔
641
                                e.getMissingChildRecipes(), compressMixedIngredients(e.getIngredientsStorage()), e.getPartialCraftingJobs());
1✔
642
                    }
643
                }
644
            }
1✔
645

646
            // Check if this dependency can be skipped
647
            if (skipDependency) {
1✔
UNCOV
648
                continue;
×
649
            }
650

651
            // If no valid crafting recipe was found for the current sub-instance, re-throw its error
652
            if (dependency == null) {
1✔
653
                missingDependencies.add(firstError);
1✔
654
                if (collectMissingRecipes) {
1✔
655
                    continue;
1✔
656
                } else {
657
                    break;
658
                }
659
            }
660

661
            // --- When we reach this point, a valid sub-recipe was found ---
662

663
            // Update our simulatedExtractionMemory to indicate that the dependency's
664
            // instance should not be extracted anymore.
665
            ((IngredientCollectionPrototypeMap<T, M>) simulatedExtractionMemory.get(dependencyComponent))
1✔
666
                    .remove(dependencyInstance);
1✔
667

668
            // If the dependency instance is reusable, mark it as available in our reusable extraction memory
669
            if (missingElement.isInputReusable()) {
1✔
670
                ((IIngredientCollectionMutable<T, M>) extractionMemoryReusable.get(dependencyComponent)).add(dependencyInstance);
1✔
671
            }
672

673
            // Add the valid sub-recipe it to our dependencies
674
            // If the recipe was already present at this level, just increment the amount of the existing job.
675
            CraftingJob existingJob = dependencies.get(dependency.getRecipe());
1✔
676
            if (existingJob == null) {
1✔
677
                dependencies.put(dependency.getRecipe(), dependency);
1✔
678
            } else {
679
                // Remove the standalone dependency job by merging it into the existing job
680
                craftingJobsGraph.mergeCraftingJobs(existingJob, dependency, true);
1✔
681
            }
682
        }
1✔
683

684
        return new PartialCraftingJobCalculationDependency(missingDependencies, dependencies.values());
1✔
685
    }
686

687
    // Helper function for calculateCraftingJobDependencyComponent
688
    protected static <T1, M1, T2, M2> void addRemainderAsSurplusForComponent(IngredientComponent<T1, M1> ingredientComponent,
689
                                                                             List<T1> instances,
690
                                                                             IngredientCollectionPrototypeMap<T1, M1> simulatedExtractionMemory,
691
                                                                             IngredientComponent<T2, M2> blackListComponent,
692
                                                                             T2 blacklistInstance, M2 blacklistCondition,
693
                                                                             long blacklistQuantity) {
694
        IIngredientMatcher<T2, M2> blacklistMatcher = blackListComponent.getMatcher();
1✔
695
        for (T1 instance : instances) {
1✔
696
            IIngredientMatcher<T1, M1> outputMatcher = ingredientComponent.getMatcher();
1✔
697
            long reduceQuantity = 0;
1✔
698
            if (blackListComponent == ingredientComponent
1✔
699
                    && blacklistMatcher.matches(blacklistInstance, (T2) instance, blacklistCondition)) {
1✔
700
                reduceQuantity = blacklistQuantity;
1✔
701
            }
702
            long quantity = simulatedExtractionMemory.getQuantity(instance) + (outputMatcher.getQuantity(instance) - reduceQuantity);
1✔
703
            if (quantity > 0) {
1✔
704
                simulatedExtractionMemory.setQuantity(instance, quantity);
1✔
705
            }
706
        }
1✔
707
    }
1✔
708

709
    /**
710
     * Schedule all crafting jobs in the given dependency graph in the given network.
711
     * @param craftingNetwork The target crafting network.
712
     * @param craftingJobDependencyGraph The crafting job dependency graph.
713
     * @param allowDistribution If the crafting jobs are allowed to be split over multiple crafting interfaces.
714
     * @param initiator Optional UUID of the initiator.
715
     * @throws UnavailableCraftingInterfacesException If no crafting interfaces were available.
716
     */
717
    public static void scheduleCraftingJobs(ICraftingNetwork craftingNetwork,
718
                                            CraftingJobDependencyGraph craftingJobDependencyGraph,
719
                                            boolean allowDistribution,
720
                                            @Nullable UUID initiator) throws UnavailableCraftingInterfacesException {
UNCOV
721
        List<CraftingJob> startedJobs = Lists.newArrayList();
×
UNCOV
722
        craftingNetwork.getCraftingJobDependencyGraph().importDependencies(craftingJobDependencyGraph);
×
723
        for (CraftingJob craftingJob : craftingJobDependencyGraph.getCraftingJobs()) {
×
724
            try {
725
                craftingNetwork.scheduleCraftingJob(craftingJob, allowDistribution);
×
UNCOV
726
            } catch (UnavailableCraftingInterfacesException e) {
×
727
                // First, cancel all jobs that were already started
728
                for (CraftingJob startedJob : startedJobs) {
×
UNCOV
729
                    craftingNetwork.cancelCraftingJob(startedJob.getChannel(), startedJob.getId());
×
730
                }
×
731

732
                // Then, throw an exception for all jobs in this dependency graph
UNCOV
733
                throw new UnavailableCraftingInterfacesException(craftingJobDependencyGraph.getCraftingJobs());
×
UNCOV
734
            }
×
735
            startedJobs.add(craftingJob);
×
736
            if (initiator != null) {
×
737
                craftingJob.setInitiatorUuid(initiator.toString());
×
738
            }
739
        }
×
UNCOV
740
    }
×
741

742
    /**
743
     * Schedule the given crafting job  in the given network.
744
     * @param craftingNetwork The target crafting network.
745
     * @param craftingJob The crafting job to schedule.
746
     * @param allowDistribution If the crafting job is allowed to be split over multiple crafting interfaces.
747
     * @param initiator Optional UUID of the initiator.
748
     * @return The scheduled crafting job.
749
     * @throws UnavailableCraftingInterfacesException If no crafting interfaces were available.
750
     */
751
    public static CraftingJob scheduleCraftingJob(ICraftingNetwork craftingNetwork,
752
                                                  CraftingJob craftingJob,
753
                                                  boolean allowDistribution,
754
                                                  @Nullable UUID initiator) throws UnavailableCraftingInterfacesException {
UNCOV
755
        craftingNetwork.scheduleCraftingJob(craftingJob, allowDistribution);
×
UNCOV
756
        if (initiator != null) {
×
757
            craftingJob.setInitiatorUuid(initiator.toString());
×
758
        }
759
        return craftingJob;
×
760
    }
761

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

UNCOV
789
            ICraftingNetwork craftingNetwork = getCraftingNetworkChecked(network);
×
790

791
            scheduleCraftingJobs(craftingNetwork, dependencyGraph, allowDistribution, initiator);
×
792

793
            return craftingJob;
×
UNCOV
794
        } catch (UnknownCraftingRecipeException | RecursiveCraftingRecipeException | UnavailableCraftingInterfacesException e) {
×
795
            return null;
×
796
        }
797
    }
798

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

UNCOV
822
            ICraftingNetwork craftingNetwork = getCraftingNetworkChecked(network);
×
823

824
            scheduleCraftingJobs(craftingNetwork, dependencyGraph, allowDistribution, initiator);
×
825

826
            return craftingJob;
×
UNCOV
827
        } catch (RecursiveCraftingRecipeException | FailedCraftingRecipeException | UnavailableCraftingInterfacesException e) {
×
828
            return null;
×
829
        }
830
    }
831

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

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

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

905
    /**
906
     * Check if there is a scheduled crafting job for the given recipe.
907
     * @param craftingNetwork The target crafting network.
908
     * @param channel The target channel.
909
     * @param recipe The recipe to check.
910
     * @return If the instance has a crafting job.
911
     */
912
    public static boolean isCrafting(ICraftingNetwork craftingNetwork, int channel,
913
                                     IRecipeDefinition recipe) {
UNCOV
914
        Iterator<CraftingJob> it = craftingNetwork.getCraftingJobs(channel);
×
UNCOV
915
        while (it.hasNext()) {
×
916
            if (it.next().getRecipe().equals(recipe)) {
×
917
                return true;
×
918
            }
919
        }
UNCOV
920
        return false;
×
921
    }
922

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

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

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

1023
        // Iterate over all input slots
1024
        List<IPrototypedIngredientAlternatives<T, M>> inputAlternativePrototypes = recipe.getInputs(ingredientComponent);
1✔
1025
        List<T> inputInstances = Lists.newArrayList();
1✔
1026
        List<MissingIngredients.Element<T, M>> missingElements =
1027
                collectMissingIngredients ? Lists.newArrayList() : null;
1✔
1028
        for (int inputIndex = 0; inputIndex < inputAlternativePrototypes.size(); inputIndex++) {
1✔
1029
            IPrototypedIngredientAlternatives<T, M> inputPrototypes = inputAlternativePrototypes.get(inputIndex);
1✔
1030
            T firstInputInstance = null;
1✔
1031
            boolean setFirstInputInstance = false;
1✔
1032
            T inputInstance = null;
1✔
1033
            boolean hasInputInstance = false;
1✔
1034
            IngredientCollectionPrototypeMap<T, M> simulatedExtractionMemoryBufferFirst = null;
1✔
1035
            IIngredientCollectionMutable<T, M> extractionMemoryReusableBufferFirst = null;
1✔
1036

1037
            // Iterate over all alternatives for this input slot, and take the first matching ingredient.
1038
            List<MissingIngredients.PrototypedWithRequested<T, M>> missingAlternatives = Lists.newArrayList();
1✔
1039
            IngredientCollectionPrototypeMap<T, M> simulatedExtractionMemoryAlternative = simulate ? new IngredientCollectionPrototypeMap<>(ingredientComponent, true) : null;
1✔
1040
            if (simulate) {
1✔
1041
                simulatedExtractionMemoryAlternative.addAll(simulatedExtractionMemory);
1✔
1042
            }
1043
            for (IPrototypedIngredient<T, M> inputPrototype : inputPrototypes.getAlternatives()) {
1✔
1044
                boolean inputReusable = recipe.isInputReusable(ingredientComponent, inputIndex);
1✔
1045
                IngredientCollectionPrototypeMap<T, M> simulatedExtractionMemoryBuffer = simulate ? new IngredientCollectionPrototypeMap<>(ingredientComponent, true) : null;
1✔
1046
                IIngredientCollectionMutable<T, M> extractionMemoryReusableBuffer = inputReusable ? new IngredientHashSet<>(ingredientComponent) : null;
1✔
1047
                boolean shouldBreak = false;
1✔
1048

1049
                // Multiply required prototype if recipe quantity is higher than one, AND if the input is NOT reusable.
1050
                if (recipeOutputQuantity > 1 && !inputReusable) {
1✔
1051
                    inputPrototype = multiplyPrototypedIngredient(inputPrototype, recipeOutputQuantity);
1✔
1052
                }
1053

1054
                // If the prototype is empty, we can skip network extraction
1055
                if (matcher.isEmpty(inputPrototype.getPrototype())) {
1✔
1056
                    inputInstance = inputPrototype.getPrototype();
1✔
1057
                    hasInputInstance = true;
1✔
1058
                    break;
1✔
1059
                }
1060

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

1101
                                missingAlternatives.add(new MissingIngredients.PrototypedWithRequested<>(inputPrototype, quantityMissingRelative));
1✔
1102
                                inputInstance = matcher.withQuantity(inputPrototype.getPrototype(), prototypeQuantity - quantityMissingRelative);
1✔
1103
                                simulatedExtractionMemoryAlternative.setQuantity(inputPrototype.getPrototype(), quantityMissingTotal);
1✔
1104
                                // 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.
1105
                                // 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)
1106
                                simulatedExtractionMemoryBuffer.add(matcher.withQuantity(inputPrototype.getPrototype(), prototypeQuantity));
1✔
1107
                            }
1108
                        } else {
1✔
1109
                            // All of our quantity can be provided via our surplus in simulatedExtractionMemory
1110
                            simulatedExtractionMemoryAlternative.add(inputPrototype.getPrototype());
1✔
1111
                            simulatedExtractionMemoryBuffer.add(inputPrototype.getPrototype());
1✔
1112
                            if (inputReusable) {
1✔
1113
                                extractionMemoryReusableBuffer.add(inputPrototype.getPrototype());
1✔
1114
                            }
1115
                            inputInstance = inputPrototype.getComponent().getMatcher().getEmptyInstance();
1✔
1116
                            hasInputInstance = true;
1✔
1117
                            shouldBreak = true;
1✔
1118
                        }
1119
                    } else {
1✔
1120
                        M matchCondition = matcher.withoutCondition(inputPrototype.getCondition(),
1✔
1121
                                ingredientComponent.getPrimaryQuantifier().getMatchCondition());
1✔
1122
                        if (storage instanceof IngredientChannelAdapter)
1✔
UNCOV
1123
                            ((IngredientChannelAdapter) storage).disableLimits();
×
1124
                        T extracted = storage.extract(inputPrototype.getPrototype(), matchCondition, simulate);
1✔
1125
                        if (storage instanceof IngredientChannelAdapter)
1✔
UNCOV
1126
                            ((IngredientChannelAdapter) storage).enableLimits();
×
1127
                        long quantityExtracted = matcher.getQuantity(extracted);
1✔
1128
                        inputInstance = extracted;
1✔
1129
                        if (simulate) {
1✔
1130
                            simulatedExtractionMemoryAlternative.add(extracted);
1✔
1131
                            simulatedExtractionMemoryBuffer.add(inputPrototype.getPrototype());
1✔
1132
                        }
1133
                        if (prototypeQuantity == quantityExtracted) {
1✔
1134
                            hasInputInstance = true;
1✔
1135
                            shouldBreak = true;
1✔
1136
                            if (inputReusable) {
1✔
1137
                                extractionMemoryReusableBuffer.add(inputPrototype.getPrototype());
1✔
1138
                            }
1139
                        } else if (collectMissingIngredients) {
1✔
1140
                            long quantityMissing = prototypeQuantity - quantityExtracted;
1✔
1141
                            missingAlternatives.add(new MissingIngredients.PrototypedWithRequested<>(inputPrototype, quantityMissing));
1✔
1142
                        }
1143
                    }
1144
                }
1145

1146
                if (!setFirstInputInstance || shouldBreak) {
1✔
1147
                    setFirstInputInstance = true;
1✔
1148
                    firstInputInstance = inputInstance;
1✔
1149
                    simulatedExtractionMemoryBufferFirst = simulatedExtractionMemoryBuffer;
1✔
1150
                    if (inputReusable) {
1✔
1151
                        extractionMemoryReusableBufferFirst = extractionMemoryReusableBuffer;
1✔
1152
                    } else {
1153
                        extractionMemoryReusableBufferFirst = null;
1✔
1154
                    }
1155
                }
1156

1157
                if (shouldBreak) {
1✔
1158
                    break;
1✔
1159
                }
1160
            }
1✔
1161

1162
            if (simulatedExtractionMemoryBufferFirst != null) {
1✔
1163
                for (T instance : simulatedExtractionMemoryBufferFirst) {
1✔
1164
                    simulatedExtractionMemory.add(instance);
1✔
1165
                }
1✔
1166
            }
1167
            if (extractionMemoryReusableBufferFirst != null) {
1✔
1168
                for (T instance : extractionMemoryReusableBufferFirst) {
1✔
1169
                    extractionMemoryReusable.add(instance);
1✔
1170
                }
1✔
1171
            }
1172

1173
            // If none of the alternatives were found, fail immediately
1174
            if (!hasInputInstance) {
1✔
1175
                if (!simulate) {
1✔
1176
                    // But first, re-insert all already-extracted instances
1177
                    for (T instance : inputInstances) {
1✔
1178
                        T remaining = storage.insert(instance, false);
1✔
1179
                        if (!matcher.isEmpty(remaining)) {
1✔
UNCOV
1180
                            throw new IllegalStateException("Extraction for a crafting recipe failed" +
×
1181
                                    "due to inconsistent insertion behaviour by destination in simulation " +
1182
                                    "and non-simulation: " + storage + ". Lost: " + remaining);
1183
                        }
1184
                    }
1✔
1185
                }
1186

1187
                if (!collectMissingIngredients) {
1✔
1188
                    // This input failed, return immediately
1189
                    return Pair.of(null, null);
1✔
1190
                } else {
1191
                    // Multiply missing collection if recipe quantity is higher than one
1192
                    if (missingAlternatives.size() > 0) {
1✔
1193
                        missingElements.add(new MissingIngredients.Element<>(missingAlternatives, recipe.isInputReusable(ingredientComponent, inputIndex)));
1✔
1194
                    }
1195
                }
1196
            }
1197

1198
            // Otherwise, append it to the list and carry on.
1199
            // If none of the instances were valid, we add the first partially valid instance.
1200
            if (hasInputInstance) {
1✔
1201
                inputInstances.add(inputInstance);
1✔
1202
            } else if (setFirstInputInstance && !matcher.isEmpty(firstInputInstance)) {
1✔
1203
                inputInstances.add(firstInputInstance);
1✔
1204
            }
1205
        }
1206

1207
        return Pair.of(
1✔
1208
                inputInstances,
1209
                collectMissingIngredients ? new MissingIngredients<>(missingElements) : null
1✔
1210
        );
1211
    }
1212

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

1241
    /**
1242
     * Create a callback function for getting a storage for an ingredient component from the given network channel.
1243
     * @param network The target network.
1244
     * @param channel The target channel.
1245
     * @param scheduleObservation If an observation inside the ingredients network should be scheduled.
1246
     * @return A callback function for getting a storage for an ingredient component.
1247
     */
1248
    public static Function<IngredientComponent<?, ?>, IIngredientComponentStorage> getNetworkStorageGetter(INetwork network, int channel, boolean scheduleObservation) {
UNCOV
1249
        return ingredientComponent -> getNetworkStorage(network, channel, ingredientComponent, scheduleObservation);
×
1250
    }
1251

1252
    /**
1253
     * Get all required recipe input ingredients based on a given storage callback.
1254
     *
1255
     * If multiple alternative inputs are possible,
1256
     * then only the first possible match will be taken.
1257
     *
1258
     * Note: Make sure that you first call in simulation-mode
1259
     * to see if the ingredients are available.
1260
     * If you immediately call this non-simulated,
1261
     * then there might be a chance that ingredients are lost
1262
     * from the network.
1263
     *
1264
     * @param storageGetter A callback function to get a storage for the given ingredient component.
1265
     * @param recipe The recipe to get the inputs from.
1266
     * @param simulate If true, then the ingredients will effectively be removed from the network, not when false.
1267
     * @param simulatedExtractionMemories This map remembers all extracted instances in simulation mode.
1268
     *                                    This is to make sure that instances can not be extracted multiple times
1269
     *                                    when simulating.
1270
     * @param extractionMemoriesReusable Like simulatedExtractionMemories, but it stores the reusable ingredients.
1271
     * @param collectMissingIngredients If missing ingredients should be collected.
1272
     *                                  If false, then the first returned mixed ingredients may be null
1273
     *                                  if no valid matches can be found,
1274
     *                                  and the second returned list is always null,
1275
     * @param recipeOutputQuantity The number of times the given recipe should be applied.
1276
     * @return A pair with two objects:
1277
     *           1. The found ingredients or null.
1278
     *           2. A mapping from ingredient component to missing ingredients (non-slot-based).
1279
     */
1280
    public static Pair<Map<IngredientComponent<?, ?>, List<?>>, Map<IngredientComponent<?, ?>, MissingIngredients<?, ?>>>
1281
    getRecipeInputs(Function<IngredientComponent<?, ?>, IIngredientComponentStorage> storageGetter, IRecipeDefinition recipe, boolean simulate,
1282
                    Map<IngredientComponent<?, ?>, IngredientCollectionPrototypeMap<?, ?>> simulatedExtractionMemories,
1283
                    Map<IngredientComponent<?, ?>, IIngredientCollectionMutable<?, ?>> extractionMemoriesReusable,
1284
                    boolean collectMissingIngredients, long recipeOutputQuantity) {
1285
        // Determine available and missing ingredients
1286
        Map<IngredientComponent<?, ?>, List<?>> ingredientsAvailable = Maps.newIdentityHashMap();
1✔
1287
        Map<IngredientComponent<?, ?>, MissingIngredients<?, ?>> ingredientsMissing = Maps.newIdentityHashMap();
1✔
1288
        for (IngredientComponent<?, ?> ingredientComponent : recipe.getInputComponents()) {
1✔
1289
            IIngredientComponentStorage storage = storageGetter.apply(ingredientComponent);
1✔
1290
            IngredientCollectionPrototypeMap<?, ?> simulatedExtractionMemory = simulatedExtractionMemories.get(ingredientComponent);
1✔
1291
            if (simulatedExtractionMemory == null) {
1✔
1292
                simulatedExtractionMemory = new IngredientCollectionPrototypeMap<>(ingredientComponent, true);
1✔
1293
                simulatedExtractionMemories.put(ingredientComponent, simulatedExtractionMemory);
1✔
1294
            }
1295
            IIngredientCollectionMutable extractionMemoryReusable = extractionMemoriesReusable.get(ingredientComponent);
1✔
1296
            if (extractionMemoryReusable == null) {
1✔
1297
                extractionMemoryReusable = new IngredientHashSet<>(ingredientComponent);
1✔
1298
                extractionMemoriesReusable.put(ingredientComponent, extractionMemoryReusable);
1✔
1299
            }
1300
            Pair<List<?>, MissingIngredients<?, ?>> subIngredients = getIngredientRecipeInputs(storage,
1✔
1301
                    (IngredientComponent) ingredientComponent, recipe, simulate, simulatedExtractionMemory, extractionMemoryReusable,
1302
                    collectMissingIngredients, recipeOutputQuantity);
1303
            List<?> subIngredientAvailable = subIngredients.getLeft();
1✔
1304
            MissingIngredients<?, ?> subIngredientsMissing = subIngredients.getRight();
1✔
1305
            if (subIngredientAvailable == null && !collectMissingIngredients) {
1✔
UNCOV
1306
                return Pair.of(null, null);
×
1307
            } else {
1308
                if (subIngredientAvailable != null && !subIngredientAvailable.isEmpty()) {
1✔
1309
                    ingredientsAvailable.put(ingredientComponent, subIngredientAvailable);
1✔
1310
                }
1311
                if (collectMissingIngredients && !subIngredientsMissing.getElements().isEmpty()) {
1✔
1312
                    ingredientsMissing.put(ingredientComponent, subIngredientsMissing);
1✔
1313
                }
1314
            }
1315
        }
1✔
1316

1317
        // Compress missing ingredients
1318
        // We do this to ensure that instances missing multiple times can be easily combined
1319
        // when triggering a crafting job for them.
1320
        Map<IngredientComponent<?, ?>, MissingIngredients<?, ?>> ingredientsMissingCompressed = Maps.newIdentityHashMap();
1✔
1321
        for (IngredientComponent<?, ?> ingredientComponent : ingredientsMissing.keySet()) {
1✔
1322
            ingredientsMissingCompressed.put(ingredientComponent, compressMissingIngredients(ingredientsMissing.get(ingredientComponent)));
1✔
1323
        }
1✔
1324

1325
        return Pair.of(ingredientsAvailable, ingredientsMissingCompressed);
1✔
1326
    }
1327

1328
    /**
1329
     * Create a list of prototyped ingredients from the instances
1330
     * of the given ingredient component type in the given mixed ingredients.
1331
     *
1332
     * Equal prototypes will be stacked.
1333
     *
1334
     * @param ingredientComponent The ingredient component type.
1335
     * @param mixedIngredients The mixed ingredients.
1336
     * @param <T> The instance type.
1337
     * @param <M> The matching condition parameter.
1338
     * @return A list of prototypes.
1339
     */
1340
    public static <T, M> List<IPrototypedIngredient<T, M>> getCompressedIngredients(IngredientComponent<T, M> ingredientComponent,
1341
                                                                                    IMixedIngredients mixedIngredients) {
1342
        List<IPrototypedIngredient<T, M>> outputs = Lists.newArrayList();
1✔
1343

1344
        IIngredientMatcher<T, M> matcher = ingredientComponent.getMatcher();
1✔
1345
        for (T instance : mixedIngredients.getInstances(ingredientComponent)) {
1✔
1346
            // Try to stack this instance with an existing prototype
1347
            boolean stacked = false;
1✔
1348
            ListIterator<IPrototypedIngredient<T, M>> existingIt = outputs.listIterator();
1✔
1349
            while(existingIt.hasNext()) {
1✔
1350
                IPrototypedIngredient<T, M> prototypedIngredient = existingIt.next();
1✔
1351
                if (matcher.matches(instance, prototypedIngredient.getPrototype(),
1✔
1352
                        prototypedIngredient.getCondition())) {
1✔
1353
                    T stackedInstance = matcher.withQuantity(prototypedIngredient.getPrototype(),
1✔
1354
                            matcher.getQuantity(prototypedIngredient.getPrototype())
1✔
1355
                                    + matcher.getQuantity(instance));
1✔
1356
                    existingIt.set(new PrototypedIngredient<>(ingredientComponent, stackedInstance,
1✔
1357
                            prototypedIngredient.getCondition()));
1✔
1358
                    stacked = true;
1✔
1359
                    break;
1✔
1360
                }
1361
            }
1✔
1362

1363
            // If not possible, just append it to the list
1364
            if (!stacked) {
1✔
1365
                outputs.add(new PrototypedIngredient<>(ingredientComponent, instance,
1✔
1366
                        matcher.getExactMatchNoQuantityCondition()));
1✔
1367
            }
1368
        }
1✔
1369

1370
        return outputs;
1✔
1371
    }
1372

1373
    /**
1374
     * Compress the given missing ingredients so that equal instances just have an incremented quantity.
1375
     *
1376
     * @param missingIngredients The missing ingredients.
1377
     * @param <T> The instance type.
1378
     * @param <M> The matching condition parameter.
1379
     * @return A new missing ingredients object.
1380
     */
1381
    public static <T, M> MissingIngredients<T, M> compressMissingIngredients(MissingIngredients<T, M> missingIngredients) {
1382
        // Index identical missing ingredients in a map, to group them by quantity
1383
        Map<MissingIngredients.Element<T, M>, Long> elementsCompressedMap = Maps.newLinkedHashMap(); // Must be a linked map to maintain our order!!!
1✔
1384
        for (MissingIngredients.Element<T, M> element : missingIngredients.getElements()) {
1✔
1385
            elementsCompressedMap.merge(element, 1L, Long::sum);
1✔
1386
        }
1✔
1387

1388
        // Create a new missing ingredients list where we multiply the missing quantities
1389
        List<MissingIngredients.Element<T, M>> elementsCompressed = Lists.newArrayList();
1✔
1390
        for (Map.Entry<MissingIngredients.Element<T, M>, Long> entry : elementsCompressedMap.entrySet()) {
1✔
1391
            Long quantity = entry.getValue();
1✔
1392
            if (quantity == 1L || entry.getKey().isInputReusable()) {
1✔
1393
                elementsCompressed.add(entry.getKey());
1✔
1394
            } else {
1395
                MissingIngredients.Element<T, M> elementOld = entry.getKey();
1✔
1396
                MissingIngredients.Element<T, M> elementNewQuantity = new MissingIngredients.Element<>(
1✔
1397
                        elementOld.getAlternatives().stream()
1✔
1398
                                .map(alt -> new MissingIngredients.PrototypedWithRequested<>(alt.getRequestedPrototype(), alt.getQuantityMissing() * quantity))
1✔
1399
                                .toList(),
1✔
1400
                        elementOld.isInputReusable()
1✔
1401
                );
1402
                elementsCompressed.add(elementNewQuantity);
1✔
1403
            }
1404
        }
1✔
1405
        return new MissingIngredients<>(elementsCompressed);
1✔
1406
    }
1407

1408
    /**
1409
     * Create a collection of prototypes from the given recipe's outputs.
1410
     *
1411
     * Equal prototypes will be stacked.
1412
     *
1413
     * @param recipe A recipe.
1414
     * @return A map from ingredient component types to their list of prototypes.
1415
     */
1416
    public static Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>> getRecipeOutputs(IRecipeDefinition recipe) {
1417
        Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>> outputs = Maps.newHashMap();
1✔
1418

1419
        IMixedIngredients mixedIngredients = recipe.getOutput();
1✔
1420
        for (IngredientComponent ingredientComponent : mixedIngredients.getComponents()) {
1✔
1421
            outputs.put(ingredientComponent, getCompressedIngredients(ingredientComponent, mixedIngredients));
1✔
1422
        }
1✔
1423

1424
        return outputs;
1✔
1425
    }
1426

1427
    /**
1428
     * Creates a new recipe outputs object with all ingredient quantities multiplied by the given amount.
1429
     * @param recipeOutputs A recipe objects holder.
1430
     * @param amount An amount to multiply all instances by.
1431
     * @return A new recipe objects holder.
1432
     */
1433
    public static Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>> multiplyRecipeOutputs(
1434
            Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>> recipeOutputs, int amount) {
1435
        if (amount == 1) {
1✔
1436
            return recipeOutputs;
1✔
1437
        }
1438

1439
        Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>> newRecipeOutputs = Maps.newIdentityHashMap();
1✔
1440
        for (Map.Entry<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>> entry : recipeOutputs.entrySet()) {
1✔
1441
            newRecipeOutputs.put(entry.getKey(), multiplyPrototypedIngredients((List) entry.getValue(), amount));
1✔
1442
        }
1✔
1443
        return newRecipeOutputs;
1✔
1444
    }
1445

1446
    /**
1447
     * Multiply the quantity of a given prototyped ingredient list with the given amount.
1448
     * @param prototypedIngredients A prototyped ingredient list.
1449
     * @param amount An amount to multiply by.
1450
     * @param <T> The instance type.
1451
     * @param <M> The matching condition parameter.
1452
     * @return A multiplied prototyped ingredient list.
1453
     */
1454
    public static <T, M> List<IPrototypedIngredient<T, M>> multiplyPrototypedIngredients(Collection<IPrototypedIngredient<T, M>> prototypedIngredients,
1455
                                                                                         long amount) {
1456
        return prototypedIngredients
1✔
1457
                .stream()
1✔
1458
                .map(p -> multiplyPrototypedIngredient(p, amount))
1✔
1459
                .collect(Collectors.toList());
1✔
1460
    }
1461

1462
    /**
1463
     * Multiply the quantity of a given prototyped ingredient with the given amount.
1464
     * @param prototypedIngredient A prototyped ingredient.
1465
     * @param amount An amount to multiply by.
1466
     * @param <T> The instance type.
1467
     * @param <M> The matching condition parameter.
1468
     * @return A multiplied prototyped ingredient.
1469
     */
1470
    public static <T, M> IPrototypedIngredient<T, M> multiplyPrototypedIngredient(IPrototypedIngredient<T, M> prototypedIngredient,
1471
                                                                                  long amount) {
1472
        IIngredientMatcher<T, M> matcher = prototypedIngredient.getComponent().getMatcher();
1✔
1473
        return new PrototypedIngredient<>(prototypedIngredient.getComponent(),
1✔
1474
                matcher.withQuantity(prototypedIngredient.getPrototype(),
1✔
1475
                        matcher.getQuantity(prototypedIngredient.getPrototype()) * amount),
1✔
1476
                prototypedIngredient.getCondition());
1✔
1477
    }
1478

1479
    /**
1480
     * Merge two mixed ingredients in a new mixed ingredients object.
1481
     * Instances will be stacked.
1482
     * @param a A first mixed ingredients object.
1483
     * @param b A second mixed ingredients object.
1484
     * @return A merged mixed ingredients object.
1485
     */
1486
    public static IMixedIngredients mergeMixedIngredients(IMixedIngredients a, IMixedIngredients b) {
1487
        // Temporarily store instances in IngredientCollectionPrototypeMaps
1488
        Map<IngredientComponent<?, ?>, IngredientCollectionQuantitativeGrouper<?, ?, IngredientArrayList<?, ?>>> groupings = Maps.newIdentityHashMap();
1✔
1489
        for (IngredientComponent<?, ?> component : a.getComponents()) {
1✔
1490
            IngredientCollectionQuantitativeGrouper grouping = new IngredientCollectionQuantitativeGrouper<>(new IngredientArrayList<>(component));
1✔
1491
            groupings.put(component, grouping);
1✔
1492
            grouping.addAll(a.getInstances(component));
1✔
1493
        }
1✔
1494
        for (IngredientComponent<?, ?> component : b.getComponents()) {
1✔
1495
            IngredientCollectionQuantitativeGrouper grouping = groupings.get(component);
1✔
1496
            if (grouping == null) {
1✔
1497
                grouping = new IngredientCollectionQuantitativeGrouper<>(new IngredientArrayList<>(component));
1✔
1498
                groupings.put(component, grouping);
1✔
1499
            }
1500
            grouping.addAll(b.getInstances(component));
1✔
1501
        }
1✔
1502

1503
        // Convert IngredientCollectionPrototypeMaps to lists
1504
        Map<IngredientComponent<?, ?>, List<?>> ingredients = Maps.newIdentityHashMap();
1✔
1505
        for (Map.Entry<IngredientComponent<?, ?>, IngredientCollectionQuantitativeGrouper<?, ?, IngredientArrayList<?, ?>>> entry : groupings.entrySet()) {
1✔
1506
            ingredients.put(entry.getKey(), Lists.newArrayList(entry.getValue()));
1✔
1507
        }
1✔
1508
        return new MixedIngredients(ingredients);
1✔
1509
    }
1510

1511
    /**
1512
     * Stack all ingredients in the given mixed ingredients object.
1513
     * @param mixedIngredients A mixed ingredients object.
1514
     * @return A new mixed ingredients object.
1515
     */
1516
    protected static IMixedIngredients compressMixedIngredients(IMixedIngredients mixedIngredients) {
1517
        // Temporarily store instances in IngredientCollectionPrototypeMaps
1518
        Map<IngredientComponent<?, ?>, IngredientCollectionQuantitativeGrouper<?, ?, IngredientArrayList<?, ?>>> groupings = Maps.newIdentityHashMap();
1✔
1519
        for (IngredientComponent<?, ?> component : mixedIngredients.getComponents()) {
1✔
1520
            IngredientCollectionQuantitativeGrouper grouping = new IngredientCollectionQuantitativeGrouper<>(new IngredientArrayList<>(component));
1✔
1521
            groupings.put(component, grouping);
1✔
1522
            grouping.addAll(mixedIngredients.getInstances(component));
1✔
1523
        }
1✔
1524

1525
        // Convert IngredientCollectionPrototypeMaps to lists
1526
        Map<IngredientComponent<?, ?>, List<?>> ingredients = Maps.newIdentityHashMap();
1✔
1527
        for (Map.Entry<IngredientComponent<?, ?>, IngredientCollectionQuantitativeGrouper<?, ?, IngredientArrayList<?, ?>>> entry : groupings.entrySet()) {
1✔
1528
            IIngredientMatcher matcher = entry.getKey().getMatcher();
1✔
1529
            List<?> values = entry.getValue()
1✔
1530
                    .stream()
1✔
1531
                    .filter(i -> !matcher.isEmpty(i))
1✔
1532
                    .collect(Collectors.toList());
1✔
1533
            if (!values.isEmpty()) {
1✔
1534
                ingredients.put(entry.getKey(), values);
1✔
1535
            }
1536
        }
1✔
1537
        return new MixedIngredients(ingredients);
1✔
1538
    }
1539

1540
    /**
1541
     * Insert the ingredients of the given ingredient component type into the target to make it start crafting.
1542
     *
1543
     * If insertion in non-simulation mode fails,
1544
     * ingredients will be re-inserted into the network.
1545
     *
1546
     * @param ingredientComponent The ingredient component type.
1547
     * @param capabilityProvider The target capability provider.
1548
     * @param side The target side.
1549
     * @param ingredients The ingredients to insert.
1550
     * @param storageFallback The storage to insert any failed ingredients back into. Can be null in simulation mode.
1551
     * @param simulate If insertion should be simulated.
1552
     * @param <T> The instance type.
1553
     * @param <M> The matching condition parameter.
1554
     * @return If all instances could be inserted.
1555
     */
1556
    public static <T, M> boolean insertIngredientCrafting(IngredientComponent<T, M> ingredientComponent,
1557
                                                          ICapabilityProvider capabilityProvider,
1558
                                                          @Nullable Direction side,
1559
                                                          IMixedIngredients ingredients,
1560
                                                          IIngredientComponentStorage<T, M> storageFallback,
1561
                                                          boolean simulate) {
UNCOV
1562
        IIngredientMatcher<T, M> matcher = ingredientComponent.getMatcher();
×
UNCOV
1563
        IIngredientComponentStorage<T, M> storage = ingredientComponent.getStorage(capabilityProvider, side);
×
1564
        List<T> instances = ingredients.getInstances(ingredientComponent);
×
1565
        List<T> failedInstances = Lists.newArrayList();
×
1566
        boolean ok = true;
×
1567
        for (T instance : instances) {
×
1568
            T remaining = storage.insert(instance, simulate);
×
1569
            if (!matcher.isEmpty(remaining)) {
×
1570
                ok = false;
×
1571
                if (!simulate) {
×
1572
                    failedInstances.add(remaining);
×
1573
                }
1574
            }
UNCOV
1575
        }
×
1576

1577
        // If we had failed insertions, try to insert them back into the network.
UNCOV
1578
        for (T instance : failedInstances) {
×
UNCOV
1579
            T remaining = storageFallback.insert(instance, false);
×
1580
            if (!matcher.isEmpty(remaining)) {
×
1581
                throw new IllegalStateException("Insertion for a crafting recipe failed" +
×
1582
                        "due to inconsistent insertion behaviour by destination in simulation " +
1583
                        "and non-simulation: " + capabilityProvider + ". Lost: " + instances);
1584
            }
UNCOV
1585
        }
×
1586

1587
        return ok;
×
1588
    }
1589

1590
    /**
1591
     * Insert the ingredients of all applicable ingredient component types into the target to make it start crafting.
1592
     *
1593
     * If insertion in non-simulation mode fails,
1594
     * ingredients will be re-inserted into the network.
1595
     *
1596
     * @param targetGetter A function to get the target position.
1597
     * @param ingredients The ingredients to insert.
1598
     * @param network The network.
1599
     * @param channel The channel.
1600
     * @param simulate If insertion should be simulated.
1601
     * @return If all instances could be inserted.
1602
     */
1603
    public static boolean insertCrafting(Function<IngredientComponent<?, ?>, PartPos> targetGetter,
1604
                                         IMixedIngredients ingredients,
1605
                                         INetwork network, int channel,
1606
                                         boolean simulate) {
UNCOV
1607
        Map<IngredientComponent<?, ?>, BlockEntity> tileMap = Maps.newIdentityHashMap();
×
1608

1609
        // First, check if we can find valid tiles for all ingredient components
UNCOV
1610
        for (IngredientComponent<?, ?> ingredientComponent : ingredients.getComponents()) {
×
UNCOV
1611
            BlockEntity tile = BlockEntityHelpers.get(targetGetter.apply(ingredientComponent).getPos(), BlockEntity.class).orElse(null);
×
1612
            if (tile != null) {
×
1613
                tileMap.put(ingredientComponent, tile);
×
1614
            } else {
1615
                return false;
×
1616
            }
1617
        }
×
1618

1619
        // Next, insert the instances into the respective tiles
UNCOV
1620
        boolean ok = true;
×
UNCOV
1621
        for (Map.Entry<IngredientComponent<?, ?>, BlockEntity> entry : tileMap.entrySet()) {
×
1622
            IIngredientComponentStorage<?, ?> storageNetwork = simulate ? null : getNetworkStorage(network, channel, entry.getKey(), false);
×
1623
            if (!insertIngredientCrafting((IngredientComponent) entry.getKey(), entry.getValue(),
×
1624
                    targetGetter.apply(entry.getKey()).getSide(), ingredients,
×
1625
                    storageNetwork, simulate)) {
1626
                ok = false;
×
1627
            }
1628
        }
×
1629

1630
        return ok;
×
1631
    }
1632

1633
    /**
1634
     * Split the given crafting job amount into new jobs with a given split factor.
1635
     * @param craftingJob A crafting job to split.
1636
     * @param splitFactor The number of jobs to split the job over.
1637
     * @param dependencyGraph The dependency graph that will be updated if there are dependencies in the original job.
1638
     * @param identifierGenerator An identifier generator for the crafting jobs ids.
1639
     * @return The newly created crafting jobs.
1640
     */
1641
    public static List<CraftingJob> splitCraftingJobs(CraftingJob craftingJob, int splitFactor,
1642
                                                      CraftingJobDependencyGraph dependencyGraph,
1643
                                                      IIdentifierGenerator identifierGenerator) {
1644
        splitFactor = Math.min(splitFactor, craftingJob.getAmount());
1✔
1645
        int division = craftingJob.getAmount() / splitFactor;
1✔
1646
        int modulus = craftingJob.getAmount() % splitFactor;
1✔
1647

1648
        // Clone original job into splitFactor jobs
1649
        List<CraftingJob> newCraftingJobs = Lists.newArrayList();
1✔
1650
        for (int i = 0; i < splitFactor; i++) {
1✔
1651
            CraftingJob clonedJob = craftingJob.clone(identifierGenerator);
1✔
1652
            newCraftingJobs.add(clonedJob);
1✔
1653

1654
            // Update amount
1655
            int newAmount = division;
1✔
1656
            if (modulus > 0) {
1✔
1657
                // No amounts will be lost, as modulus is guaranteed to be smaller than splitFactor
1658
                newAmount++;
1✔
1659
                modulus--;
1✔
1660
            }
1661
            clonedJob.setAmount(newAmount);
1✔
1662
        }
1663

1664
        // Collect dependency links
1665
        Collection<CraftingJob> originalDependencies = dependencyGraph.getDependencies(craftingJob);
1✔
1666
        Collection<CraftingJob> originalDependents = dependencyGraph.getDependents(craftingJob);
1✔
1667

1668
        // Remove dependency links to and from the original jobs
1669
        for (CraftingJob dependency : originalDependencies) {
1✔
1670
            craftingJob.removeDependency(dependency);
1✔
1671
            dependencyGraph.removeDependency(craftingJob.getId(), dependency.getId());
1✔
1672
        }
1✔
1673
        for (CraftingJob dependent : originalDependents) {
1✔
1674
            dependent.removeDependency(craftingJob);
1✔
1675
            dependencyGraph.removeDependency(dependent.getId(), craftingJob.getId());
1✔
1676
        }
1✔
1677

1678
        // Create dependency links to and from the new crafting jobs
1679
        for (CraftingJob dependency : originalDependencies) {
1✔
1680
            for (CraftingJob newCraftingJob : newCraftingJobs) {
1✔
1681
                newCraftingJob.addDependency(dependency);
1✔
1682
                dependencyGraph.addDependency(newCraftingJob, dependency);
1✔
1683
            }
1✔
1684
        }
1✔
1685
        for (CraftingJob originalDependent : originalDependents) {
1✔
1686
            for (CraftingJob newCraftingJob : newCraftingJobs) {
1✔
1687
                originalDependent.addDependency(newCraftingJob);
1✔
1688
                dependencyGraph.addDependency(originalDependent, newCraftingJob);
1✔
1689
            }
1✔
1690
        }
1✔
1691

1692
        return newCraftingJobs;
1✔
1693
    }
1694

1695
    /**
1696
     * Insert the given ingredients into the given storage networks.
1697
     * @param ingredients A collection of ingredients.
1698
     * @param storageGetter A storage network getter.
1699
     * @param simulate If insertion should be simulated.
1700
     * @return The remaining ingredients that were not inserted.
1701
     */
1702
    public static IMixedIngredients insertIngredients(IMixedIngredients ingredients,
1703
                                                      Function<IngredientComponent<?, ?>, IIngredientComponentStorage> storageGetter,
1704
                                                      boolean simulate) {
UNCOV
1705
        Map<IngredientComponent<?, ?>, List<?>> remainingIngredients = Maps.newIdentityHashMap();
×
UNCOV
1706
        for (IngredientComponent<?, ?> component : ingredients.getComponents()) {
×
1707
            IIngredientComponentStorage storage = storageGetter.apply(component);
×
1708
            IIngredientMatcher matcher = component.getMatcher();
×
1709
            for (Object instance : ingredients.getInstances(component)) {
×
1710
                Object remainder = storage.insert(instance, simulate);
×
1711
                if (!matcher.isEmpty(remainder)) {
×
1712
                    List remainingInstances = remainingIngredients.get(component);
×
1713
                    if (remainingInstances == null) {
×
1714
                        remainingInstances = Lists.newArrayList();
×
1715
                        remainingIngredients.put(component, remainingInstances);
×
1716
                    }
1717
                    remainingInstances.add(instance);
×
1718
                }
1719
            }
×
UNCOV
1720
        }
×
1721
        return new MixedIngredients(remainingIngredients);
×
1722
    }
1723

1724
    /**
1725
     * Generates semi-unique IDs.
1726
     */
1727
    public static interface IIdentifierGenerator {
1728
        public int getNext();
1729
    }
1730

1731
}
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

© 2025 Coveralls, Inc