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

CyclopsMC / IntegratedCrafting / #479011830

31 Dec 2025 08:35AM UTC coverage: 24.029% (-0.3%) from 24.3%
#479011830

push

github

rubensworks
Fix missing ingredients not taken into account when piping job results to dependents

0 of 51 new or added lines in 4 files covered. (0.0%)

4 existing lines in 3 files now uncovered.

755 of 3142 relevant lines covered (24.03%)

0.24 hits per line

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

26.7
/src/main/java/org/cyclops/integratedcrafting/api/crafting/CraftingJob.java
1
package org.cyclops.integratedcrafting.api.crafting;
2

3
import com.google.common.collect.Maps;
4
import it.unimi.dsi.fastutil.ints.IntArrayList;
5
import it.unimi.dsi.fastutil.ints.IntList;
6
import net.minecraft.nbt.CompoundTag;
7
import net.minecraft.nbt.IntArrayTag;
8
import net.minecraft.nbt.Tag;
9
import org.apache.commons.compress.utils.Lists;
10
import org.cyclops.commoncapabilities.api.capability.recipehandler.IRecipeDefinition;
11
import org.cyclops.commoncapabilities.api.ingredient.IIngredientMatcher;
12
import org.cyclops.commoncapabilities.api.ingredient.IMixedIngredients;
13
import org.cyclops.commoncapabilities.api.ingredient.IngredientComponent;
14
import org.cyclops.commoncapabilities.api.ingredient.MixedIngredients;
15
import org.cyclops.cyclopscore.ingredient.collection.IngredientList;
16
import org.cyclops.cyclopscore.ingredient.storage.IngredientComponentStorageSlottedCollectionWrapper;
17
import org.cyclops.integratedcrafting.core.CraftingHelpers;
18
import org.cyclops.integratedcrafting.core.MissingIngredients;
19

20
import javax.annotation.Nullable;
21
import java.util.Iterator;
22
import java.util.List;
23
import java.util.Map;
24
import java.util.Objects;
25

26
/**
27
 * @author rubensworks
28
 */
29
public class CraftingJob {
30

31
    private final int id;
32
    private final int channel;
33
    private final IRecipeDefinition recipe;
34
    private final IntList dependencyCraftingJobs;
35
    private final IntList dependentCraftingJobs;
36
    private int amount;
37
    private IMixedIngredients ingredientsStorage; // Total to extract from storage (simulated and immutable)
38
    private IMixedIngredients ingredientsStorageBuffer; // The actual ingredients from storage, which are consumed over time.
39
    private Map<IngredientComponent<?, ?>, MissingIngredients<?, ?>> lastMissingIngredients;
40
    private long startTick;
41
    private boolean invalidInputs;
42
    @Nullable
43
    private String initiatorUuid;
44
    private boolean ignoreDependencyCheck;
45

46
    public CraftingJob(int id, int channel, IRecipeDefinition recipe, int amount, IMixedIngredients ingredientsStorage) {
1✔
47
        this.id = id;
1✔
48
        this.channel = channel;
1✔
49
        this.recipe = recipe;
1✔
50
        this.amount = amount;
1✔
51
        this.ingredientsStorage = ingredientsStorage;
1✔
52
        this.ingredientsStorageBuffer = new MixedIngredients(Maps.newIdentityHashMap());
1✔
53
        this.lastMissingIngredients = Maps.newIdentityHashMap();
1✔
54
        this.dependencyCraftingJobs = new IntArrayList();
1✔
55
        this.dependentCraftingJobs = new IntArrayList();
1✔
56
        this.invalidInputs = false;
1✔
57
        this.ignoreDependencyCheck = false;
1✔
58
    }
1✔
59

60
    public int getId() {
61
        return id;
1✔
62
    }
63

64
    public int getChannel() {
65
        return this.channel;
1✔
66
    }
67

68
    public IRecipeDefinition getRecipe() {
69
        return this.recipe;
1✔
70
    }
71

72
    public IntList getDependencyCraftingJobs() {
73
        return dependencyCraftingJobs;
1✔
74
    }
75

76
    public IntList getDependentCraftingJobs() {
77
        return dependentCraftingJobs;
1✔
78
    }
79

80
    public int getAmount() {
81
        return amount;
1✔
82
    }
83

84
    public void setAmount(int amount) {
85
        this.amount = amount;
1✔
86
    }
1✔
87

88
    public void addDependency(CraftingJob dependency) {
89
        dependencyCraftingJobs.add(dependency.getId());
1✔
90
        dependency.dependentCraftingJobs.add(this.getId());
1✔
91
    }
1✔
92

93
    public void removeDependency(CraftingJob dependency) {
94
        dependencyCraftingJobs.rem(dependency.getId());
1✔
95
        dependency.dependentCraftingJobs.rem(this.getId());
1✔
96
    }
1✔
97

98
    /**
99
     * @return The ingredients that will be taken from storage
100
     *         The amount of this job is already taken into account.
101
     */
102
    public IMixedIngredients getIngredientsStorage() {
103
        return ingredientsStorage;
1✔
104
    }
105

106
    public void setIngredientsStorage(IMixedIngredients ingredientsStorage) {
107
        this.ingredientsStorage = ingredientsStorage;
1✔
108
    }
1✔
109

110
    public IMixedIngredients getIngredientsStorageBuffer() {
111
        return ingredientsStorageBuffer;
1✔
112
    }
113

114
    public void setIngredientsStorageBuffer(IMixedIngredients ingredientsStorageBuffer) {
115
        this.ingredientsStorageBuffer = ingredientsStorageBuffer;
×
116
    }
×
117

118
    public <T, M> long getMissingIngredientQuantity(IngredientComponent<T, M> ingredientComponent, T instance) {
NEW
119
        long quantityMissing = 0;
×
NEW
120
        MissingIngredients<?, ?> missingIngredients = this.lastMissingIngredients.get(ingredientComponent);
×
NEW
121
        if (missingIngredients != null) {
×
NEW
122
            IIngredientMatcher<T, M> matcher = ingredientComponent.getMatcher();
×
NEW
123
            for (MissingIngredients.Element<?, ?> element : missingIngredients.getElements()) {
×
NEW
124
                for (MissingIngredients.PrototypedWithRequested<?, ?> alternative : element.getAlternatives()) {
×
NEW
125
                    if (matcher.matches(instance, (T) alternative.getRequestedPrototype().getPrototype(), matcher.withoutCondition((M) alternative.getRequestedPrototype().getCondition(), ingredientComponent.getPrimaryQuantifier().getMatchCondition()))) {
×
NEW
126
                        quantityMissing += alternative.getQuantityMissing();
×
127
                    }
NEW
128
                }
×
NEW
129
            }
×
130
        }
NEW
131
        return quantityMissing;
×
132
    }
133

134
    public <T, M> void addToIngredientsStorageBuffer(IngredientComponent<T, M> ingredientComponent, T instance) {
135
        // Add instance to the buffer
136
        IIngredientMatcher<T, M> matcher = ingredientComponent.getMatcher();
×
137
        IMixedIngredients buffer = this.getIngredientsStorageBuffer();
×
138
        if (!buffer.getComponents().contains(ingredientComponent)) {
×
139
            Map<IngredientComponent<?, ?>, List<?>> mixedIngredientsRaw = Maps.newIdentityHashMap();
×
140
            for (IngredientComponent<?, ?> component : buffer.getComponents()) {
×
141
                mixedIngredientsRaw.put(component, buffer.getInstances(component));
×
142
            }
×
143
            List<T> list = Lists.newArrayList();
×
144
            mixedIngredientsRaw.put(ingredientComponent, list);
×
145
            list.add(instance);
×
146
            buffer = new MixedIngredients(mixedIngredientsRaw);
×
147
            this.setIngredientsStorageBuffer(buffer);
×
148
        } else {
×
149
            List<T> instances = buffer.getInstances(ingredientComponent);
×
150
            if (!instances.stream().anyMatch(matcher::isEmpty)) {
×
151
                // Make sure we have at least one empty slot available, to guarantee insertion can succeed.
152
                instances.add(matcher.getEmptyInstance());
×
153
            }
154
            T remaining = new IngredientComponentStorageSlottedCollectionWrapper<>(new IngredientList<>(ingredientComponent, instances), Integer.MAX_VALUE, Integer.MAX_VALUE).insert(instance, false);
×
155
            if (!matcher.isEmpty(remaining)) {
×
156
                throw new IllegalStateException(String.format("Unable to insert %s into the crafting job buffer, remaining: ", instances, remaining));
×
157
            }
158
        }
159

160
        // If the instance was a missing ingredient, remove it
NEW
161
        long instanceQuantity = matcher.getQuantity(instance);
×
NEW
162
        MissingIngredients<?, ?> missingIngredients = this.lastMissingIngredients.get(ingredientComponent);
×
NEW
163
        if (missingIngredients != null) {
×
NEW
164
            Iterator<? extends MissingIngredients.Element<?, ?>> it = missingIngredients.getElements().iterator();
×
NEW
165
            boolean removed = false;
×
NEW
166
            while (it.hasNext() && instanceQuantity > 0) {
×
NEW
167
                MissingIngredients.Element<?, ?> element = it.next();
×
NEW
168
                for (MissingIngredients.PrototypedWithRequested<?, ?> alternative : element.getAlternatives()) {
×
NEW
169
                    if (matcher.matches(instance, (T) alternative.getRequestedPrototype().getPrototype(), matcher.withoutCondition((M) alternative.getRequestedPrototype().getCondition(), ingredientComponent.getPrimaryQuantifier().getMatchCondition()))) {
×
NEW
170
                        long missingQuantityToConsume = Math.min(alternative.getQuantityMissing(), instanceQuantity);
×
NEW
171
                        alternative.setQuantityMissing(alternative.getQuantityMissing() - missingQuantityToConsume);
×
NEW
172
                        instanceQuantity -= missingQuantityToConsume;
×
NEW
173
                        if (alternative.getQuantityMissing() == 0) {
×
NEW
174
                            removed = true;
×
NEW
175
                            it.remove();
×
176
                        }
177
                        break;
178
                    }
NEW
179
                }
×
NEW
180
            }
×
NEW
181
            if (removed) {
×
NEW
182
                if (missingIngredients.getElements().isEmpty()) {
×
NEW
183
                    this.lastMissingIngredients.remove(ingredientComponent);
×
184
                }
185
            }
186
        }
UNCOV
187
    }
×
188

189
    /**
190
     * @return The ingredients that were missing for 1 job amount. This will mostly be an empty map.
191
     */
192
    public Map<IngredientComponent<?, ?>, MissingIngredients<?, ?>> getLastMissingIngredients() {
193
        return lastMissingIngredients;
×
194
    }
195

196
    public void setLastMissingIngredients(Map<IngredientComponent<?, ?>, MissingIngredients<?, ?>> lastMissingIngredients) {
197
        this.lastMissingIngredients = lastMissingIngredients;
×
198
    }
×
199

200
    public long getStartTick() {
201
        return startTick;
×
202
    }
203

204
    public void setStartTick(long startTick) {
205
        this.startTick = startTick;
×
206
    }
×
207

208
    public boolean isInvalidInputs() {
209
        return invalidInputs;
×
210
    }
211

212
    public void setInvalidInputs(boolean invalidInputs) {
213
        this.invalidInputs = invalidInputs;
×
214
    }
×
215

216
    @Nullable
217
    public String getInitiatorUuid() {
218
        return initiatorUuid;
×
219
    }
220

221
    public void setInitiatorUuid(String initiatorUuid) {
222
        this.initiatorUuid = initiatorUuid;
×
223
    }
×
224

225
    public void setIgnoreDependencyCheck(boolean ignoreDependencyCheck) {
226
        this.ignoreDependencyCheck = ignoreDependencyCheck;
×
227
    }
×
228

229
    public boolean isIgnoreDependencyCheck() {
230
        return ignoreDependencyCheck;
×
231
    }
232

233
    public static CompoundTag serialize(CraftingJob craftingJob) {
234
        CompoundTag tag = new CompoundTag();
×
235
        tag.putInt("id", craftingJob.id);
×
236
        tag.putInt("channel", craftingJob.channel);
×
237
        tag.put("recipe", IRecipeDefinition.serialize(craftingJob.recipe));
×
238
        tag.put("dependencies", new IntArrayTag(craftingJob.getDependencyCraftingJobs()));
×
239
        tag.put("dependents", new IntArrayTag(craftingJob.getDependentCraftingJobs()));
×
240
        tag.putInt("amount", craftingJob.amount);
×
241
        tag.put("ingredientsStorage", IMixedIngredients.serialize(craftingJob.ingredientsStorage));
×
242
        tag.put("ingredientsStorageBuffer", IMixedIngredients.serialize(craftingJob.ingredientsStorageBuffer));
×
243
        tag.put("lastMissingIngredients", MissingIngredients.serialize(craftingJob.lastMissingIngredients));
×
244
        tag.putLong("startTick", craftingJob.startTick);
×
245
        tag.putBoolean("invalidInputs", craftingJob.invalidInputs);
×
246
        if (craftingJob.initiatorUuid != null) {
×
247
            tag.putString("initiatorUuid", craftingJob.initiatorUuid);
×
248
        }
249
        tag.putBoolean("ignoreDependencyCheck", craftingJob.ignoreDependencyCheck);
×
250
        return tag;
×
251
    }
252

253
    public static CraftingJob deserialize(CompoundTag tag) {
254
        if (!tag.contains("id", Tag.TAG_INT)) {
×
255
            throw new IllegalArgumentException("Could not find an id entry in the given tag");
×
256
        }
257
        if (!tag.contains("channel", Tag.TAG_INT)) {
×
258
            throw new IllegalArgumentException("Could not find a channel entry in the given tag");
×
259
        }
260
        if (!tag.contains("recipe", Tag.TAG_COMPOUND)) {
×
261
            throw new IllegalArgumentException("Could not find a recipe entry in the given tag");
×
262
        }
263
        if (!tag.contains("dependencies", Tag.TAG_INT_ARRAY)) {
×
264
            throw new IllegalArgumentException("Could not find a dependencies entry in the given tag");
×
265
        }
266
        if (!tag.contains("dependents", Tag.TAG_INT_ARRAY)) {
×
267
            throw new IllegalArgumentException("Could not find a dependents entry in the given tag");
×
268
        }
269
        if (!tag.contains("amount", Tag.TAG_INT)) {
×
270
            throw new IllegalArgumentException("Could not find a amount entry in the given tag");
×
271
        }
272
        if (!tag.contains("ingredientsStorage", Tag.TAG_COMPOUND)) {
×
273
            throw new IllegalArgumentException("Could not find a ingredientsStorage entry in the given tag");
×
274
        }
275
        if (!tag.contains("lastMissingIngredients", Tag.TAG_COMPOUND)) {
×
276
            throw new IllegalArgumentException("Could not find a lastMissingIngredients entry in the given tag");
×
277
        }
278
        if (!tag.contains("startTick", Tag.TAG_LONG)) {
×
279
            throw new IllegalArgumentException("Could not find a startTick entry in the given tag");
×
280
        }
281
        if (!tag.contains("invalidInputs", Tag.TAG_BYTE)) {
×
282
            throw new IllegalArgumentException("Could not find an invalidInputs entry in the given tag");
×
283
        }
284
        int id = tag.getInt("id");
×
285
        int channel = tag.getInt("channel");
×
286
        IRecipeDefinition recipe = IRecipeDefinition.deserialize(tag.getCompound("recipe"));
×
287
        int amount = tag.getInt("amount");
×
288
        IMixedIngredients ingredientsStorage = IMixedIngredients.deserialize(tag.getCompound("ingredientsStorage"));
×
289
        IMixedIngredients ingredientsStorageBuffer = IMixedIngredients.deserialize(tag.contains("ingredientsStorageBuffer") ? tag.getCompound("ingredientsStorageBuffer") : new CompoundTag()); // TODO: rm backwards-compat in nextmajor
×
290
        CraftingJob craftingJob = new CraftingJob(id, channel, recipe, amount, ingredientsStorage);
×
291
        for (int dependency : tag.getIntArray("dependencies")) {
×
292
            craftingJob.dependencyCraftingJobs.add(dependency);
×
293
        }
294
        for (int dependent : tag.getIntArray("dependents")) {
×
295
            craftingJob.dependentCraftingJobs.add(dependent);
×
296
        }
297
        Map<IngredientComponent<?, ?>, MissingIngredients<?, ?>> lastMissingIngredients = MissingIngredients
×
298
                .deserialize(tag.getCompound("lastMissingIngredients"));
×
299
        craftingJob.setLastMissingIngredients(lastMissingIngredients);
×
300
        craftingJob.setStartTick(tag.getLong("startTick"));
×
301
        craftingJob.setInvalidInputs(tag.getBoolean("invalidInputs"));
×
302
        if (tag.contains("initiatorUuid", Tag.TAG_STRING)) {
×
303
            craftingJob.setInitiatorUuid(tag.getString("initiatorUuid"));
×
304
        }
305
        craftingJob.setIgnoreDependencyCheck(tag.getBoolean("ignoreDependencyCheck"));
×
306
        craftingJob.setIngredientsStorageBuffer(ingredientsStorageBuffer);
×
307
        return craftingJob;
×
308
    }
309

310
    @Override
311
    public String toString() {
312
        return String.format("[Crafting Job id: %s, channel: %s, recipe: %s, dependencies: %s, dependents: %s, amount: %s, storage: %s]",
×
313
                getId(), getChannel(), getRecipe(), getDependencyCraftingJobs(), getDependentCraftingJobs(), getAmount(), getIngredientsStorage());
×
314
    }
315

316
    @Override
317
    public boolean equals(Object obj) {
318
        if (!(obj instanceof CraftingJob)) {
1✔
319
            return false;
×
320
        }
321
        CraftingJob that = (CraftingJob) obj;
1✔
322
        return this.getId() == that.getId()
1✔
323
                && this.getChannel() == that.getChannel()
1✔
324
                && Objects.equals(this.getRecipe(), that.getRecipe())
1✔
325
                && this.getDependencyCraftingJobs().equals(that.getDependencyCraftingJobs())
1✔
326
                && this.getDependentCraftingJobs().equals(that.getDependentCraftingJobs())
1✔
327
                && this.getAmount() == that.getAmount()
1✔
328
                && this.getIngredientsStorage().equals(that.getIngredientsStorage());
1✔
329
    }
330

331
    public CraftingJob clone(CraftingHelpers.IIdentifierGenerator identifierGenerator) {
332
        if (!this.getIngredientsStorageBuffer().isEmpty()) {
1✔
333
            throw new IllegalStateException("Cloning a job with an ingredient buffer is illegal");
×
334
        }
335
        return new CraftingJob(
1✔
336
                identifierGenerator.getNext(),
1✔
337
                getChannel(),
1✔
338
                getRecipe(),
1✔
339
                getAmount(),
1✔
340
                getIngredientsStorage()
1✔
341
        );
342
    }
343
}
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