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

CyclopsMC / IntegratedDynamics / 18384427956

09 Oct 2025 05:46PM UTC coverage: 53.031% (-0.04%) from 53.071%
18384427956

push

github

rubensworks
Merge remote-tracking branch 'origin/master-1.21-lts' into master-1.21

2863 of 8762 branches covered (32.68%)

Branch coverage included in aggregate %.

17326 of 29308 relevant lines covered (59.12%)

3.07 hits per line

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

0.0
/src/main/java/org/cyclops/integrateddynamics/core/logicprogrammer/ValueTypeRecipeLPElement.java
1
package org.cyclops.integrateddynamics.core.logicprogrammer;
2

3
import com.google.common.collect.Lists;
4
import com.google.common.collect.Maps;
5
import lombok.Getter;
6
import lombok.Setter;
7
import net.minecraft.ResourceLocationException;
8
import net.minecraft.client.Minecraft;
9
import net.minecraft.core.HolderSet;
10
import net.minecraft.core.NonNullList;
11
import net.minecraft.core.registries.BuiltInRegistries;
12
import net.minecraft.core.registries.Registries;
13
import net.minecraft.network.chat.Component;
14
import net.minecraft.resources.ResourceLocation;
15
import net.minecraft.tags.TagKey;
16
import net.minecraft.world.Container;
17
import net.minecraft.world.entity.player.Player;
18
import net.minecraft.world.inventory.ClickType;
19
import net.minecraft.world.inventory.Slot;
20
import net.minecraft.world.item.Item;
21
import net.minecraft.world.item.ItemStack;
22
import net.minecraft.world.item.Items;
23
import net.neoforged.neoforge.capabilities.Capabilities;
24
import net.neoforged.neoforge.fluids.FluidStack;
25
import net.neoforged.neoforge.fluids.capability.IFluidHandler;
26
import net.neoforged.neoforge.fluids.capability.IFluidHandlerItem;
27
import org.cyclops.commoncapabilities.api.capability.fluidhandler.FluidMatch;
28
import org.cyclops.commoncapabilities.api.capability.recipehandler.IPrototypedIngredientAlternatives;
29
import org.cyclops.commoncapabilities.api.capability.recipehandler.IRecipeDefinition;
30
import org.cyclops.commoncapabilities.api.capability.recipehandler.PrototypedIngredientAlternativesList;
31
import org.cyclops.commoncapabilities.api.capability.recipehandler.RecipeDefinition;
32
import org.cyclops.commoncapabilities.api.ingredient.IPrototypedIngredient;
33
import org.cyclops.commoncapabilities.api.ingredient.IngredientComponent;
34
import org.cyclops.commoncapabilities.api.ingredient.MixedIngredients;
35
import org.cyclops.commoncapabilities.api.ingredient.PrototypedIngredient;
36
import org.cyclops.cyclopscore.helper.IModHelpers;
37
import org.cyclops.cyclopscore.helper.IModHelpersNeoForge;
38
import org.cyclops.cyclopscore.inventory.slot.SlotExtended;
39
import org.cyclops.integrateddynamics.IntegratedDynamics;
40
import org.cyclops.integrateddynamics.api.evaluate.variable.IValue;
41
import org.cyclops.integrateddynamics.api.logicprogrammer.IConfigRenderPattern;
42
import org.cyclops.integrateddynamics.api.logicprogrammer.ILogicProgrammerElementType;
43
import org.cyclops.integrateddynamics.core.evaluate.variable.ValueObjectTypeRecipe;
44
import org.cyclops.integrateddynamics.core.evaluate.variable.ValueTypes;
45
import org.cyclops.integrateddynamics.core.helper.Helpers;
46
import org.cyclops.integrateddynamics.core.helper.L10NValues;
47
import org.cyclops.integrateddynamics.core.ingredient.ItemMatchProperties;
48
import org.cyclops.integrateddynamics.core.logicprogrammer.client.ValueTypeRecipeLPElementClient;
49
import org.cyclops.integrateddynamics.inventory.container.ContainerLogicProgrammerBase;
50
import org.cyclops.integrateddynamics.network.packet.LogicProgrammerValueTypeRecipeSlotPropertiesChangedPacket;
51

52
import java.util.Collections;
53
import java.util.List;
54
import java.util.Map;
55
import java.util.Optional;
56
import java.util.function.Consumer;
57
import java.util.stream.Collectors;
58

59
/**
60
 * Element for recipes.
61
 * This is hardcoded to only support items, fluids and energy
62
 * @author rubensworks
63
 */
64
public class ValueTypeRecipeLPElement extends ValueTypeLPElementBase<ValueTypeRecipeLPElementClient> {
65

66
    public static final int SLOT_OFFSET = 4;
67
    public static final int TICK_DELAY = 30;
68

69
    @Getter
×
70
    private NonNullList<ItemMatchProperties> inputStacks;
71
    private ItemStack inputFluid;
72
    @Getter
×
73
    @Setter
×
74
    private String inputFluidAmount = "0";
75
    @Getter
×
76
    @Setter
×
77
    private String inputEnergy = "0";
78
    private NonNullList<ItemStack> outputStacks;
79
    private ItemStack outputFluid;
80
    @Getter
×
81
    @Setter
×
82
    private String outputFluidAmount = "0";
83
    @Getter
×
84
    @Setter
×
85
    private String outputEnergy = "0";
86

87
    public ValueTypeRecipeLPElement() {
88
        super(ValueTypes.OBJECT_RECIPE);
×
89
    }
×
90

91
    @Override
92
    public ValueTypeRecipeLPElementClient constructClient() {
93
        return new ValueTypeRecipeLPElementClient(this);
×
94
    }
95

96
    @Override
97
    public ILogicProgrammerElementType getType() {
98
        return LogicProgrammerElementTypes.VALUETYPE;
×
99
    }
100

101
    @Override
102
    public IConfigRenderPattern getRenderPattern() {
103
        return IConfigRenderPattern.RECIPE;
×
104
    }
105

106
    @Override
107
    public void onInputSlotUpdated(Player player, int slotId, ItemStack itemStack) {
108
        if (inputStacks == null) {
×
109
            return;
×
110
        }
111

112
        if (slotId >= 0 && slotId < 9) {
×
113
            ItemStack itemStackOld = inputStacks.get(slotId).getItemStack();
×
114
            if (itemStackOld.getItem() != itemStack.getItem()) {
×
115
                inputStacks.set(slotId, new ItemMatchProperties(itemStack.copy()));
×
116
                if (IModHelpers.get().getMinecraftHelpers().isClientSideThread()) {
×
117
                    getClient().refreshPropertiesGui(slotId);
×
118
                }
119
            }
120
        }
121
        if (slotId == 9) {
×
122
            inputFluid = itemStack.copy();
×
123
            if (inputFluidAmount.equalsIgnoreCase("0")) {
×
124
                int amount = IModHelpersNeoForge.get().getFluidHelpers().getAmount(Helpers.getFluidStack(inputFluid));
×
125
                inputFluidAmount = Integer.toString(amount);
×
126
                if (IModHelpers.get().getMinecraftHelpers().isClientSideThread()) {
×
127
                    getClient().refreshInputFluidAmountBox();
×
128
                }
129
            }
130
        }
131
        if (slotId > 9 && slotId < 13) {
×
132
            outputStacks.set(slotId - 10, itemStack.copy());
×
133
        }
134
        if (slotId == 13) {
×
135
            outputFluid = itemStack.copy();
×
136
            if (outputFluidAmount.equalsIgnoreCase("0")) {
×
137
                int amount = IModHelpersNeoForge.get().getFluidHelpers().getAmount(Helpers.getFluidStack(outputFluid));
×
138
                outputFluidAmount = Integer.toString(amount);
×
139
                if (IModHelpers.get().getMinecraftHelpers().isClientSideThread()) {
×
140
                    getClient().refreshOutputFluidAmountBox();
×
141
                }
142
            }
143
        }
144
    }
×
145

146
    public void sendSlotPropertiesToServer(int slotId, ItemMatchProperties props) {
147
        IntegratedDynamics._instance.getPacketHandler().sendToServer(
×
148
                new LogicProgrammerValueTypeRecipeSlotPropertiesChangedPacket(
149
                        slotId, props.isNbt(), props.getItemTag() == null ? "" : props.getItemTag(), props.getTagQuantity(), props.isReusable()));
×
150
    }
×
151

152
    // Used by ID-Compat for JEI recipe transfer handler
153
    public boolean isValidForRecipeGrid(List<ItemMatchProperties> itemInputs, List<FluidStack> fluidInputs,
154
                                        List<ItemStack> itemOutputs, List<FluidStack> fluidOutputs) {
155
        return itemInputs.size() <= 9 && itemOutputs.size() <= 3
×
156
                && fluidInputs.size() <= 1 && fluidOutputs.size() <= 1;
×
157
    }
158

159
    protected void putItemPropertiesInContainer(ContainerLogicProgrammerBase container, int slot, ItemMatchProperties props) {
160
        putStackInContainer(container, slot, props.getItemStack());
×
161
        getInputStacks().set(slot, props);
×
162
    }
×
163

164
    protected void putStackInContainer(ContainerLogicProgrammerBase container, int slot, ItemStack itemStack) {
165
        // Offset: Player inventory, recipe grid slots
166
        container.setItem(container.getItems().size() - (36 + 14) + slot, 0, itemStack);
×
167
    }
×
168

169
    // Used by ID-Compat for JEI recipe transfer handler
170
    public void setRecipeGrid(ContainerLogicProgrammerBase container,
171
                              List<ItemMatchProperties> itemInputs, List<FluidStack> fluidInputs,
172
                              List<ItemStack> itemOutputs, List<FluidStack> fluidOutputs) {
173
        int slot = 0;
×
174

175
        // Fill input item slots
176
        for (ItemMatchProperties itemInput : itemInputs) {
×
177
            putItemPropertiesInContainer(container, slot, itemInput);
×
178
            slot++;
×
179
        }
×
180
        while (slot < 9) {
×
181
            putItemPropertiesInContainer(container, slot, new ItemMatchProperties(ItemStack.EMPTY));
×
182
            slot++;
×
183
        }
184

185
        // Fill input fluid slot
186
        slot = 9;
×
187
        FluidStack fluidStackInput = FluidStack.EMPTY;
×
188
        if (fluidInputs.size() > 0) {
×
189
            fluidStackInput = fluidInputs.get(0);
×
190
        }
191
        putStackInContainer(container, slot, fluidStackInput.isEmpty() ? ItemStack.EMPTY : getFluidBucket(fluidStackInput));
×
192
        inputFluidAmount = String.valueOf(IModHelpersNeoForge.get().getFluidHelpers().getAmount(fluidStackInput));
×
193
        if (IModHelpers.get().getMinecraftHelpers().isClientSideThread()) {
×
194
            getClient().refreshInputFluidAmountBox();
×
195
        }
196

197
        // Fill input output slots
198
        slot = 10;
×
199
        for (ItemStack itemOutput : itemOutputs) {
×
200
            putStackInContainer(container, slot, itemOutput);
×
201
            slot++;
×
202
        }
×
203
        while (slot < 13) {
×
204
            putStackInContainer(container, slot, ItemStack.EMPTY);
×
205
            slot++;
×
206
        }
207

208
        // Fill output fluid slot
209
        slot = 13;
×
210
        FluidStack fluidStackOutput = FluidStack.EMPTY;
×
211
        if (fluidOutputs.size() > 0) {
×
212
            fluidStackOutput = fluidOutputs.get(0);
×
213
        }
214
        putStackInContainer(container, slot, fluidStackOutput.isEmpty() ? ItemStack.EMPTY : getFluidBucket(fluidStackOutput));
×
215
        outputFluidAmount = String.valueOf(IModHelpersNeoForge.get().getFluidHelpers().getAmount(fluidStackOutput));
×
216
        if (IModHelpers.get().getMinecraftHelpers().isClientSideThread()) {
×
217
            getClient().refreshOutputFluidAmountBox();
×
218
        }
219
    }
×
220

221
    public static ItemStack getFluidBucket(FluidStack fluidStack) {
222
        ItemStack itemStack = new ItemStack(Items.BUCKET);
×
223
        IFluidHandlerItem fluidHandler = itemStack.getCapability(Capabilities.FluidHandler.ITEM);
×
224
        fluidHandler.fill(new FluidStack(fluidStack.getFluid(), IModHelpersNeoForge.get().getFluidHelpers().getBucketVolume()), IFluidHandler.FluidAction.EXECUTE);
×
225
        return fluidHandler.getContainer();
×
226
    }
227

228
    protected boolean isInputValid() {
229
        return inputStacks.stream().anyMatch(ItemMatchProperties::isValid)
×
230
                || !inputFluid.isEmpty() || !inputFluidAmount.equalsIgnoreCase("0")
×
231
                || !inputEnergy.equalsIgnoreCase("0");
×
232
    }
233

234
    protected boolean isOutputValid() {
235
        return outputStacks.stream().anyMatch(stack -> !stack.isEmpty())
×
236
                || !outputFluid.isEmpty() || !outputFluidAmount.equalsIgnoreCase("0")
×
237
                || !outputEnergy.equalsIgnoreCase("0");
×
238
    }
239

240
    @Override
241
    public boolean canWriteElementPre() {
242
        return isInputValid() == isOutputValid(); // Not &&, because we also allow fully blank recipes
×
243
    }
244

245
    @Override
246
    public void activate() {
247
        inputStacks = NonNullList.withSize(9, new ItemMatchProperties(ItemStack.EMPTY));
×
248
        for (int i = 0; i < 9; i++) {
×
249
            inputStacks.set(i, new ItemMatchProperties(ItemStack.EMPTY));
×
250
        }
251
        inputFluid = ItemStack.EMPTY;
×
252
        inputFluidAmount = "0";
×
253
        inputEnergy = "0";
×
254
        outputStacks = NonNullList.withSize(3, ItemStack.EMPTY);
×
255
        outputFluid = ItemStack.EMPTY;
×
256
        outputFluidAmount = "0";
×
257
        outputEnergy = "0";
×
258
    }
×
259

260
    @Override
261
    public void deactivate() {
262

263
    }
×
264

265
    @Override
266
    public Component validate() {
267
        if (!inputFluid.isEmpty() && Helpers.getFluidStack(inputFluid).isEmpty()) {
×
268
            return Component.translatable(L10NValues.VALUETYPE_OBJECT_FLUID_ERROR_NOFLUID);
×
269
        }
270
        if (!outputFluid.isEmpty() && Helpers.getFluidStack(outputFluid).isEmpty()) {
×
271
            return Component.translatable(L10NValues.VALUETYPE_OBJECT_FLUID_ERROR_NOFLUID);
×
272
        }
273
        try {
274
            Integer.parseInt(inputFluidAmount);
×
275
        } catch (NumberFormatException e) {
×
276
            return Component.translatable(L10NValues.VALUETYPE_ERROR_INVALIDINPUT, inputFluidAmount);
×
277
        }
×
278
        try {
279
            Integer.parseInt(outputFluidAmount);
×
280
        } catch (NumberFormatException e) {
×
281
            return Component.translatable(L10NValues.VALUETYPE_ERROR_INVALIDINPUT, outputFluidAmount);
×
282
        }
×
283
        try {
284
            Long.parseLong(inputEnergy);
×
285
        } catch (NumberFormatException e) {
×
286
            return Component.translatable(L10NValues.VALUETYPE_ERROR_INVALIDINPUT, inputEnergy);
×
287
        }
×
288
        try {
289
            Long.parseLong(outputEnergy);
×
290
        } catch (NumberFormatException e) {
×
291
            return Component.translatable(L10NValues.VALUETYPE_ERROR_INVALIDINPUT, outputEnergy);
×
292
        }
×
293
        // Validate input item tag strings if they are defined
294
        for (ItemMatchProperties inputStack : inputStacks) {
×
295
            if (inputStack.getItemTag() != null) {
×
296
                try {
297
                    ResourceLocation.parse(inputStack.getItemTag());
×
298
                } catch (ResourceLocationException e) {
×
299
                    return Component.translatable(L10NValues.VALUETYPE_ERROR_INVALIDINPUT, inputStack.getItemTag());
×
300
                }
×
301
            }
302
        }
×
303
        return null;
×
304
    }
305

306
    @Override
307
    public boolean isItemValidForSlot(int slotId, ItemStack itemStack) {
308
        return true;
×
309
    }
310

311
    @Override
312
    public boolean slotClick(int slotId, Slot slot, int mouseButton, ClickType clickType, Player player) {
313
        return slotClickCommon(slotId, slot, mouseButton, clickType, player, getInputStacks(), 9, (i) -> {
×
314
            if (IModHelpers.get().getMinecraftHelpers().isClientSideThread()) {
×
315
                getClient().setPropertySubGui(i);
×
316
            }
317
        }, (i) -> {
×
318
            if (IModHelpers.get().getMinecraftHelpers().isClientSideThread()) {
×
319
                getClient().refreshPropertiesGui(i);
×
320
            }
321
        }) || super.slotClick(slotId, slot, mouseButton, clickType, player);
×
322
    }
323

324
    public static boolean slotClickCommon(int slotId, Slot slot, int mouseButton, ClickType clickType, Player player,
325
                                          List<ItemMatchProperties> inputStacks, int propertySlotCount,
326
                                          Consumer<Integer> setPropertySubGui,
327
                                          Consumer<Integer> refreshPropertiesGui) {
328
        if (slotId >= SLOT_OFFSET && slotId < propertySlotCount + SLOT_OFFSET) {
×
329
            if (clickType == ClickType.QUICK_MOVE && mouseButton == 0) {
×
330
                if (player.level().isClientSide()) {
×
331
                    int id = slotId - SLOT_OFFSET;
×
332
                    setPropertySubGui.accept(id);
×
333
                }
334
                return true;
×
335
            } else {
336
                // Similar logic as ContainerExtended.adjustPhantomSlot
337
                ItemMatchProperties props = inputStacks.get(slotId - SLOT_OFFSET);
×
338
                int quantityCurrent = props.getTagQuantity();
×
339
                int quantityNew;
340
                if (clickType == ClickType.QUICK_MOVE) {
×
341
                    quantityNew = mouseButton == 0 ? (quantityCurrent + 1) / 2 : quantityCurrent * 2;
×
342
                } else {
343
                    quantityNew = mouseButton == 0 ? quantityCurrent - 1 : quantityCurrent + 1;
×
344
                }
345

346
                if (quantityNew > slot.getMaxStackSize()) {
×
347
                    quantityNew = slot.getMaxStackSize();
×
348
                }
349

350
                props.setTagQuantity(quantityNew);
×
351
                if (!props.getItemStack().isEmpty()) {
×
352
                    props.getItemStack().setCount(quantityNew);
×
353
                }
354

355
                if (quantityNew <= 0) {
×
356
                    props.setItemTag(null);
×
357
                    props.setTagQuantity(1);
×
358
                    if (IModHelpers.get().getMinecraftHelpers().isClientSideThread()) {
×
359
                        refreshPropertiesGui.accept(slotId - SLOT_OFFSET);
×
360
                    }
361
                }
362
            }
363
        }
364

365
        return false;
×
366
    }
367

368
    @Override
369
    public Slot createSlot(Container temporaryInputSlots, int slotId, int x, int y) {
370
        SlotExtended slot = new SlotExtended(temporaryInputSlots, slotId, x, y) {
×
371
            @Override
372
            public boolean mayPlace(ItemStack itemStack) {
373
                return ValueTypeRecipeLPElement.this.isItemValidForSlot(slotId, itemStack);
×
374
            }
375

376
            @Override
377
            public ItemStack getItem() {
378
                if (IModHelpers.get().getMinecraftHelpers().isClientSideThread() && slotId < 9) {
×
379
                    return getRotatingItemFromTag(getInputStacks().get(slotId))
×
380
                            .orElseGet(super::getItem);
×
381
                }
382
                return super.getItem();
×
383
            }
384
        };
385
        slot.setPhantom(true);
×
386
        return slot;
×
387
    }
388

389
    public static Optional<ItemStack> getRotatingItemFromTag(ItemMatchProperties props) {
390
        String tagName = props.getItemTag();
×
391
        if (tagName != null) {
×
392
            try {
393
                Optional<HolderSet.Named<Item>> tag = BuiltInRegistries.ITEM.get(TagKey.create(Registries.ITEM, ResourceLocation.parse(tagName)));
×
394
                if (!tag.isEmpty()) {
×
395
                    List<Item> items = tag.stream()
×
396
                            .flatMap(holders -> holders.stream())
×
397
                            .map(holder -> holder.value()).toList();
×
398
                    int tick = ((int) Minecraft.getInstance().level.getGameTime()) / TICK_DELAY;
×
399
                    Item item = items.get(tick % items.size());
×
400
                    return Optional.of(new ItemStack(item, props.getTagQuantity()));
×
401
                }
402
            } catch (ResourceLocationException e) {
×
403
                // Ignore invalid tags
404
            }
×
405
        }
406
        return Optional.empty();
×
407
    }
408

409
    @Override
410
    public int getItemStackSizeLimit() {
411
        return 64;
×
412
    }
413

414
    protected Map<IngredientComponent<?, ?>, List<IPrototypedIngredientAlternatives<?, ?>>> getInputs(List<ItemMatchProperties> itemStacks,
415
                                                                                                      ItemStack fluid, int fluidAmount,
416
                                                                                                      long energy) {
417
        // Cut of itemStacks list until last non-empty stack
418
        int lastNonEmpty = 0;
×
419
        for (int i = 0; i < itemStacks.size(); i++) {
×
420
            if (itemStacks.get(i).isValid()) {
×
421
                lastNonEmpty = i + 1;
×
422
            }
423
        }
424
        itemStacks = itemStacks.subList(0, lastNonEmpty);
×
425

426
        // Override fluid amount
427
        FluidStack fluidStack = Helpers.getFluidStack(fluid);
×
428
        if (!fluidStack.isEmpty()) {
×
429
            fluidStack.setAmount(fluidAmount);
×
430
        }
431

432
        Map<IngredientComponent<?, ?>, List<IPrototypedIngredientAlternatives<?, ?>>> inputs = Maps.newIdentityHashMap();
×
433
        List<IPrototypedIngredientAlternatives<ItemStack, Integer>> items = itemStacks.stream()
×
434
                .map(ItemMatchProperties::createPrototypedIngredient)
×
435
                .collect(Collectors.toList());
×
436
        List<IPrototypedIngredientAlternatives<FluidStack, Integer>> fluids = !fluidStack.isEmpty()
×
437
                ? Collections.singletonList(new PrototypedIngredientAlternativesList<>(
×
438
                        Collections.singletonList(new PrototypedIngredient<>(IngredientComponent.FLUIDSTACK, fluidStack, FluidMatch.FLUID | FluidMatch.DATA))))
×
439
                : Collections.emptyList();
×
440
        List<IPrototypedIngredientAlternatives<Long, Boolean>> energies = energy > 0 ?
×
441
                Collections.singletonList(new PrototypedIngredientAlternativesList<>(
×
442
                        Collections.singletonList(new PrototypedIngredient<>(IngredientComponent.ENERGY, energy, false))))
×
443
                : Collections.emptyList();
×
444
        if (!items.isEmpty()) {
×
445
            inputs.put(IngredientComponent.ITEMSTACK, (List) items);
×
446
        }
447
        if (!fluids.isEmpty()) {
×
448
            inputs.put(IngredientComponent.FLUIDSTACK, (List) fluids);
×
449
        }
450
        if (!energies.isEmpty()) {
×
451
            inputs.put(IngredientComponent.ENERGY, (List) energies);
×
452
        }
453

454
        return inputs;
×
455
    }
456

457
    public static Map<IngredientComponent<?, ?>, List<Boolean>> getInputsReusable(List<ItemMatchProperties> itemStacks) {
458
        Map<IngredientComponent<?, ?>, List<Boolean>> inputs = Maps.newIdentityHashMap();
×
459

460
        List<Boolean> items = itemStacks.stream()
×
461
                .map(ItemMatchProperties::isReusable)
×
462
                .collect(Collectors.toList());
×
463
        if (!items.isEmpty()) {
×
464
            inputs.put(IngredientComponent.ITEMSTACK, (List) items);
×
465
        }
466

467
        return inputs;
×
468
    }
469

470
    protected Map<IngredientComponent<?, ?>, List<?>> getOutputs(List<ItemStack> itemStacksIn,
471
                                                                 ItemStack fluid, int fluidAmount,
472
                                                                 long energy) {
473
        // Cut of itemStacks list until last non-empty stack
474
        List<ItemStack> itemStacks = Lists.newArrayList();
×
475
        for (int i = 0; i < itemStacksIn.size(); i++) {
×
476
            if (!itemStacksIn.get(i).isEmpty()) {
×
477
                itemStacks.add(itemStacksIn.get(i));
×
478
            }
479
        }
480

481
        // Override fluid amount
482
        FluidStack fluidStack = Helpers.getFluidStack(fluid);
×
483
        if (!fluidStack.isEmpty()) {
×
484
            fluidStack.setAmount(fluidAmount);
×
485
        }
486

487
        Map<IngredientComponent<?, ?>, List<?>> outputs = Maps.newIdentityHashMap();
×
488
        if (!itemStacks.isEmpty()) {
×
489
            outputs.put(IngredientComponent.ITEMSTACK, itemStacks);
×
490
        }
491
        if (!fluidStack.isEmpty()) {
×
492
            outputs.put(IngredientComponent.FLUIDSTACK, Collections.singletonList(fluidStack));
×
493
        }
494
        if (energy > 0) {
×
495
            outputs.put(IngredientComponent.ENERGY, Collections.singletonList(energy));
×
496
        }
497

498
        return outputs;
×
499
    }
500

501
    @Override
502
    public IValue getValue() {
503
        if (!isInputValid() && !isOutputValid()) {
×
504
            return ValueObjectTypeRecipe.ValueRecipe.of(null);
×
505
        }
506
        return ValueObjectTypeRecipe.ValueRecipe.of(
×
507
                new RecipeDefinition(getInputs(this.inputStacks, this.inputFluid,
×
508
                        Integer.parseInt(this.inputFluidAmount), Long.parseLong(this.inputEnergy)),
×
509
                getInputsReusable(this.inputStacks),
×
510
                new MixedIngredients(getOutputs(this.outputStacks, this.outputFluid,
×
511
                        Integer.parseInt(this.outputFluidAmount), Long.parseLong(this.outputEnergy)))));
×
512
    }
513

514
    @Override
515
    public void setValue(IValue value) {
516
        ValueObjectTypeRecipe.ValueRecipe valueRecipe = (ValueObjectTypeRecipe.ValueRecipe) value;
×
517
        valueRecipe.getRawValue().ifPresent(recipe -> {
×
518
            loadInputStacks(recipe);
×
519
            loadInputFluid(recipe);
×
520
            loadInputEnergy(recipe);
×
521

522
            loadOutputStacks(recipe);
×
523
            loadOutputFluid(recipe);
×
524
            loadOutputEnergy(recipe);
×
525
        });
×
526
    }
×
527

528
    private void loadInputStacks(IRecipeDefinition recipe) {
529
        List<IPrototypedIngredientAlternatives<ItemStack, Integer>> listAlternatives = recipe.getInputs(IngredientComponent.ITEMSTACK);
×
530
        for (int i = 0; i < listAlternatives.size(); i++) {
×
531
            IPrototypedIngredientAlternatives<ItemStack, Integer> prototypes = listAlternatives.get(i);
×
532
            boolean reusable = recipe.isInputReusable(IngredientComponent.ITEMSTACK, i);
×
533
            ItemMatchProperties itemMatchProperties = ItemMatchProperties.fromPrototypedIngredient(prototypes, reusable);
×
534
            this.inputStacks.set(i, itemMatchProperties);
×
535
        }
536
    }
×
537

538
    private <T, M> Optional<T> loadFirstInput(IRecipeDefinition recipe, IngredientComponent<T, M> ingredientComponent) {
539
        List<IPrototypedIngredientAlternatives<T, M>> listAlternatives = recipe.getInputs(ingredientComponent);
×
540
        if (listAlternatives.size() > 0) {
×
541
            IPrototypedIngredientAlternatives<T, M> prototypes = listAlternatives.get(0);
×
542
            if (prototypes.getAlternatives().size() > 0) {
×
543
                IPrototypedIngredient<T, M> prototype = prototypes.getAlternatives().stream().findFirst().get();
×
544
                return Optional.of(prototype.getPrototype());
×
545
            }
546
        }
547
        return Optional.empty();
×
548
    }
549

550
    private void loadInputFluid(IRecipeDefinition recipe) {
551
        loadFirstInput(recipe, IngredientComponent.FLUIDSTACK).ifPresent(fluidStack -> {
×
552
            this.inputFluid = fluidStack.getFluid().getFluidType().getBucket(fluidStack);
×
553
            this.inputFluidAmount = Integer.toString(fluidStack.getAmount());
×
554
        });
×
555
    }
×
556

557
    private void loadInputEnergy(IRecipeDefinition recipe) {
558
        loadFirstInput(recipe, IngredientComponent.ENERGY).ifPresent(energy -> this.inputEnergy = Long.toString(energy));
×
559
    }
×
560

561
    private void loadOutputStacks(IRecipeDefinition recipe) {
562
        List<ItemStack> instances = recipe.getOutput().getInstances(IngredientComponent.ITEMSTACK);
×
563
        if (instances.size() > 0) {
×
564
            outputStacks.set(0, instances.get(0));
×
565
        }
566
        if (instances.size() > 1) {
×
567
            outputStacks.set(1, instances.get(1));
×
568
        }
569
        if (instances.size() > 2) {
×
570
            outputStacks.set(2, instances.get(2));
×
571
        }
572
    }
×
573

574
    private <T, M> Optional<T> loadFirstOutput(IRecipeDefinition recipe, IngredientComponent<T, M> ingredientComponent) {
575
        List<T> instances = recipe.getOutput().getInstances(ingredientComponent);
×
576
        if (instances.size() > 0) {
×
577
            return Optional.of(instances.get(0));
×
578
        }
579
        return Optional.empty();
×
580
    }
581

582
    private void loadOutputFluid(IRecipeDefinition recipe) {
583
        loadFirstOutput(recipe, IngredientComponent.FLUIDSTACK).ifPresent(fluidStack -> {
×
584
            this.outputFluid = fluidStack.getFluid().getFluidType().getBucket(fluidStack);
×
585
            this.outputFluidAmount = Integer.toString(fluidStack.getAmount());
×
586
        });
×
587
    }
×
588

589
    private void loadOutputEnergy(IRecipeDefinition recipe) {
590
        loadFirstOutput(recipe, IngredientComponent.ENERGY).ifPresent(energy -> this.outputEnergy = Long.toString(energy));
×
591
    }
×
592

593
    @Override
594
    public void setValueInContainer(ContainerLogicProgrammerBase container) {
595
        Container slots = container.getTemporaryInputSlots();
×
596

597
        // Input slots
598
        for (int i = 0; i < this.inputStacks.size(); i++) {
×
599
            ItemMatchProperties entry = this.inputStacks.get(i);
×
600
            slots.setItem(i, entry.getItemStack());
×
601
        }
602
        slots.setItem(9, this.inputFluid);
×
603

604
        // Output slots
605
        for (int i = 0; i < this.outputStacks.size(); i++) {
×
606
            slots.setItem(10 + i, this.outputStacks.get(i));
×
607
            // No need to set slot type, as this can't be changed for output stacks
608
        }
609
        slots.setItem(13, this.outputFluid);
×
610
    }
×
611

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

© 2026 Coveralls, Inc