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

CyclopsMC / IntegratedCrafting / #479011708

12 Sep 2023 03:31PM UTC coverage: 25.098% (-0.2%) from 25.268%
#479011708

push

github-actions

rubensworks
Fix jobs not finishing if output is extracted outside network

Closes #98

This fixes the following scenario:
  hopper: 1 item
  world-observer: detect 1 item
  hopper: extract 1 item from chest
    --- missing observer!
  crafter: add 1 item to hopper
  world-observer: observe no difference
    --- no change is detected in crafter!

19 of 19 new or added lines in 1 file covered. (100.0%)

706 of 2813 relevant lines covered (25.1%)

0.25 hits per line

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

0.0
/src/main/java/org/cyclops/integratedcrafting/part/PartTypeInterfaceCrafting.java
1
package org.cyclops.integratedcrafting.part;
2

3
import com.google.common.collect.Lists;
4
import com.google.common.collect.MapMaker;
5
import it.unimi.dsi.fastutil.ints.Int2BooleanArrayMap;
6
import it.unimi.dsi.fastutil.ints.Int2BooleanMap;
7
import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
8
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
9
import it.unimi.dsi.fastutil.ints.IntArraySet;
10
import it.unimi.dsi.fastutil.ints.IntSet;
11
import net.minecraft.core.Direction;
12
import net.minecraft.nbt.CompoundTag;
13
import net.minecraft.nbt.ListTag;
14
import net.minecraft.nbt.Tag;
15
import net.minecraft.network.FriendlyByteBuf;
16
import net.minecraft.network.chat.MutableComponent;
17
import net.minecraft.network.chat.TranslatableComponent;
18
import net.minecraft.resources.ResourceLocation;
19
import net.minecraft.server.level.ServerPlayer;
20
import net.minecraft.world.MenuProvider;
21
import net.minecraft.world.SimpleContainer;
22
import net.minecraft.world.entity.player.Inventory;
23
import net.minecraft.world.entity.player.Player;
24
import net.minecraft.world.inventory.AbstractContainerMenu;
25
import net.minecraft.world.item.ItemStack;
26
import net.minecraft.world.level.block.state.BlockState;
27
import net.minecraftforge.common.MinecraftForge;
28
import net.minecraftforge.common.capabilities.Capability;
29
import net.minecraftforge.common.util.LazyOptional;
30
import org.apache.commons.lang3.tuple.Triple;
31
import org.apache.logging.log4j.Level;
32
import org.cyclops.commoncapabilities.api.capability.block.BlockCapabilities;
33
import org.cyclops.commoncapabilities.api.capability.recipehandler.IRecipeDefinition;
34
import org.cyclops.commoncapabilities.api.capability.recipehandler.IRecipeHandler;
35
import org.cyclops.commoncapabilities.api.ingredient.IIngredientMatcher;
36
import org.cyclops.commoncapabilities.api.ingredient.IMixedIngredients;
37
import org.cyclops.commoncapabilities.api.ingredient.IPrototypedIngredient;
38
import org.cyclops.commoncapabilities.api.ingredient.IngredientComponent;
39
import org.cyclops.commoncapabilities.api.ingredient.IngredientInstanceWrapper;
40
import org.cyclops.commoncapabilities.api.ingredient.MixedIngredients;
41
import org.cyclops.commoncapabilities.api.ingredient.storage.IIngredientComponentStorage;
42
import org.cyclops.cyclopscore.datastructure.DimPos;
43
import org.cyclops.cyclopscore.helper.BlockEntityHelpers;
44
import org.cyclops.cyclopscore.inventory.SimpleInventory;
45
import org.cyclops.cyclopscore.persist.nbt.NBTClassType;
46
import org.cyclops.integratedcrafting.Capabilities;
47
import org.cyclops.integratedcrafting.GeneralConfig;
48
import org.cyclops.integratedcrafting.IntegratedCrafting;
49
import org.cyclops.integratedcrafting.api.crafting.CraftingJob;
50
import org.cyclops.integratedcrafting.api.crafting.CraftingJobStatus;
51
import org.cyclops.integratedcrafting.api.crafting.ICraftingInterface;
52
import org.cyclops.integratedcrafting.api.crafting.ICraftingResultsSink;
53
import org.cyclops.integratedcrafting.api.network.ICraftingNetwork;
54
import org.cyclops.integratedcrafting.capability.network.CraftingInterfaceConfig;
55
import org.cyclops.integratedcrafting.capability.network.CraftingNetworkConfig;
56
import org.cyclops.integratedcrafting.core.CraftingHelpers;
57
import org.cyclops.integratedcrafting.core.CraftingJobHandler;
58
import org.cyclops.integratedcrafting.core.CraftingProcessOverrides;
59
import org.cyclops.integratedcrafting.core.part.PartTypeCraftingBase;
60
import org.cyclops.integratedcrafting.ingredient.storage.IngredientComponentStorageSlottedInsertProxy;
61
import org.cyclops.integratedcrafting.inventory.container.ContainerPartInterfaceCrafting;
62
import org.cyclops.integratedcrafting.inventory.container.ContainerPartInterfaceCraftingSettings;
63
import org.cyclops.integrateddynamics.api.evaluate.EvaluationException;
64
import org.cyclops.integrateddynamics.api.evaluate.variable.IValue;
65
import org.cyclops.integrateddynamics.api.evaluate.variable.IVariable;
66
import org.cyclops.integrateddynamics.api.network.INetwork;
67
import org.cyclops.integrateddynamics.api.network.INetworkIngredientsChannel;
68
import org.cyclops.integrateddynamics.api.network.IPartNetwork;
69
import org.cyclops.integrateddynamics.api.network.IPositionedAddonsNetworkIngredients;
70
import org.cyclops.integrateddynamics.api.part.IPartContainer;
71
import org.cyclops.integrateddynamics.api.part.PartPos;
72
import org.cyclops.integrateddynamics.api.part.PartTarget;
73
import org.cyclops.integrateddynamics.api.part.PrioritizedPartPos;
74
import org.cyclops.integrateddynamics.capability.network.PositionedAddonsNetworkIngredientsHandlerConfig;
75
import org.cyclops.integrateddynamics.core.evaluate.InventoryVariableEvaluator;
76
import org.cyclops.integrateddynamics.core.evaluate.variable.ValueObjectTypeRecipe;
77
import org.cyclops.integrateddynamics.core.evaluate.variable.ValueTypes;
78
import org.cyclops.integrateddynamics.core.helper.NetworkHelpers;
79
import org.cyclops.integrateddynamics.core.helper.PartHelpers;
80
import org.cyclops.integrateddynamics.core.part.PartStateBase;
81
import org.cyclops.integrateddynamics.core.part.PartTypeBase;
82
import org.cyclops.integrateddynamics.core.part.event.PartVariableDrivenVariableContentsUpdatedEvent;
83

84
import javax.annotation.Nullable;
85
import java.util.Collection;
86
import java.util.Iterator;
87
import java.util.List;
88
import java.util.ListIterator;
89
import java.util.Map;
90
import java.util.Optional;
91

92
/**
93
 * Interface for item handlers.
94
 * @author rubensworks
95
 */
96
public class PartTypeInterfaceCrafting extends PartTypeCraftingBase<PartTypeInterfaceCrafting, PartTypeInterfaceCrafting.State> {
97

98
    public PartTypeInterfaceCrafting(String name) {
99
        super(name);
×
100
    }
×
101

102
    @Override
103
    public int getConsumptionRate(State state) {
104
        return state.getCraftingJobHandler().getProcessingCraftingJobs().size() * GeneralConfig.interfaceCraftingBaseConsumption;
×
105
    }
106

107
    @Override
108
    public Optional<MenuProvider> getContainerProvider(PartPos pos) {
109
        return Optional.of(new MenuProvider() {
×
110

111
            @Override
112
            public MutableComponent getDisplayName() {
113
                return new TranslatableComponent(getTranslationKey());
×
114
            }
115

116
            @Override
117
            public AbstractContainerMenu createMenu(int id, Inventory playerInventory, Player playerEntity) {
118
                Triple<IPartContainer, PartTypeBase, PartTarget> data = PartHelpers.getContainerPartConstructionData(pos);
×
119
                PartTypeInterfaceCrafting.State partState = (PartTypeInterfaceCrafting.State) data.getLeft().getPartState(data.getRight().getCenter().getSide());
×
120
                return new ContainerPartInterfaceCrafting(id, playerInventory, partState.getInventoryVariables(),
×
121
                        Optional.of(data.getRight()), Optional.of(data.getLeft()), (PartTypeInterfaceCrafting) data.getMiddle());
×
122
            }
123
        });
124
    }
125

126
    @Override
127
    public void writeExtraGuiData(FriendlyByteBuf packetBuffer, PartPos pos, ServerPlayer player) {
128
        // Write inventory size
129
        IPartContainer partContainer = PartHelpers.getPartContainerChecked(pos);
×
130
        PartTypeInterfaceCrafting.State partState = (PartTypeInterfaceCrafting.State) partContainer.getPartState(pos.getSide());
×
131
        packetBuffer.writeInt(partState.getInventoryVariables().getContainerSize());
×
132

133
        super.writeExtraGuiData(packetBuffer, pos, player);
×
134
    }
×
135

136
    @Override
137
    public Optional<MenuProvider> getContainerProviderSettings(PartPos pos) {
138
        return Optional.of(new MenuProvider() {
×
139

140
            @Override
141
            public MutableComponent getDisplayName() {
142
                return new TranslatableComponent(getTranslationKey());
×
143
            }
144

145
            @Override
146
            public AbstractContainerMenu createMenu(int id, Inventory playerInventory, Player playerEntity) {
147
                Triple<IPartContainer, PartTypeBase, PartTarget> data = PartHelpers.getContainerPartConstructionData(pos);
×
148
                return new ContainerPartInterfaceCraftingSettings(id, playerInventory, new SimpleContainer(0),
×
149
                        data.getRight(), Optional.of(data.getLeft()), data.getMiddle());
×
150
            }
151
        });
152
    }
153

154
    @Override
155
    protected PartTypeInterfaceCrafting.State constructDefaultState() {
156
        return new PartTypeInterfaceCrafting.State();
×
157
    }
158

159
    @Override
160
    public void afterNetworkReAlive(INetwork network, IPartNetwork partNetwork, PartTarget target, PartTypeInterfaceCrafting.State state) {
161
        super.afterNetworkReAlive(network, partNetwork, target, state);
×
162
        addTargetToNetwork(network, target, state);
×
163
    }
×
164

165
    @Override
166
    public void onNetworkRemoval(INetwork network, IPartNetwork partNetwork, PartTarget target, PartTypeInterfaceCrafting.State state) {
167
        super.onNetworkRemoval(network, partNetwork, target, state);
×
168
        removeTargetFromNetwork(network, target.getTarget(), state);
×
169
    }
×
170

171
    @Override
172
    public void onNetworkAddition(INetwork network, IPartNetwork partNetwork, PartTarget target, PartTypeInterfaceCrafting.State state) {
173
        super.onNetworkAddition(network, partNetwork, target, state);
×
174
        addTargetToNetwork(network, target, state);
×
175
    }
×
176

177
    @Override
178
    public void setPriorityAndChannel(INetwork network, IPartNetwork partNetwork, PartTarget target, PartTypeInterfaceCrafting.State state, int priority, int channel) {
179
        // We need to do this because the crafting network is not automagically aware of the priority changes,
180
        // so we have to re-add it.
181
        removeTargetFromNetwork(network, target.getTarget(), state);
×
182
        super.setPriorityAndChannel(network, partNetwork, target, state, priority, channel);
×
183
        addTargetToNetwork(network, target, state);
×
184
    }
×
185

186
    protected Capability<ICraftingNetwork> getNetworkCapability() {
187
        return CraftingNetworkConfig.CAPABILITY;
×
188
    }
189

190
    protected void addTargetToNetwork(INetwork network, PartTarget pos, PartTypeInterfaceCrafting.State state) {
191
        network.getCapability(getNetworkCapability())
×
192
                .ifPresent(craftingNetwork -> {
×
193
                    int channelCrafting = state.getChannelCrafting();
×
194
                    state.setTarget(pos);
×
195
                    state.setNetworks(network, craftingNetwork, NetworkHelpers.getPartNetworkChecked(network), channelCrafting);
×
196
                    state.setShouldAddToCraftingNetwork(true);
×
197
                });
×
198
    }
×
199

200
    protected void removeTargetFromNetwork(INetwork network, PartPos pos, PartTypeInterfaceCrafting.State state) {
201
        ICraftingNetwork craftingNetwork = state.getCraftingNetwork();
×
202
        if (craftingNetwork != null) {
×
203
            network.getCapability(getNetworkCapability())
×
204
                    .ifPresent(n -> n.removeCraftingInterface(state.getChannelCrafting(), state));
×
205
        }
206
        state.setNetworks(null, null, null, -1);
×
207
        state.setTarget(null);
×
208
    }
×
209

210
    @Override
211
    public boolean isUpdate(State state) {
212
        return true;
×
213
    }
214

215
    @Override
216
    public int getMinimumUpdateInterval(State state) {
217
        return state.getDefaultUpdateInterval();
×
218
    }
219

220
    @Override
221
    public void update(INetwork network, IPartNetwork partNetwork, PartTarget target, State state) {
222
        super.update(network, partNetwork, target, state);
×
223

224
        // Init network data in part state if it has not been done yet.
225
        // This can occur when the part chunk is being reloaded.
226
        if (state.getCraftingNetwork() == null) {
×
227
            addTargetToNetwork(network, target, state);
×
228
        }
229

230
        int channel = state.getChannelCrafting();
×
231

232
        // Update the network data in the part state
233
        if (state.shouldAddToCraftingNetwork()) {
×
234
            ICraftingNetwork craftingNetwork = network.getCapability(getNetworkCapability()).orElse(null);
×
235
            craftingNetwork.addCraftingInterface(channel, state);
×
236
            state.setShouldAddToCraftingNetwork(false);
×
237
        }
238

239
        // Push any pending output ingredients into the network
240
        state.flushInventoryOutputBuffer(network);
×
241

242
        // Block job ticking if there still are outputs in our crafting result buffer.
243
        if (state.getInventoryOutputBuffer().isEmpty()) {
×
244
            // Tick the job handler
245
            PartPos targetPos = state.getTarget().getTarget();
×
246
            state.getCraftingJobHandler().update(network, channel, targetPos);
×
247
        }
248

249
        // Reload recipes if needed
250
        IntSet slots = state.getDelayedRecipeReloads();
×
251
        if (!slots.isEmpty()) {
×
252
            ICraftingNetwork craftingNetwork = network.getCapability(getNetworkCapability()).orElse(null);
×
253
            if (craftingNetwork != null) {
×
254
                for (Integer slot : slots) {
×
255
                    // Remove the old recipe from the network
256
                    Int2ObjectMap<IRecipeDefinition> recipes = state.getRecipesIndexed();
×
257
                    IRecipeDefinition oldRecipe = recipes.get(slot);
×
258
                    if (oldRecipe != null) {
×
259
                        craftingNetwork.removeCraftingInterfaceRecipe(channel, state, oldRecipe);
×
260
                    }
261

262
                    // Reload the recipe in the slot
263
                    state.reloadRecipe(slot);
×
264

265
                    // Add the new recipe to the network
266
                    IRecipeDefinition newRecipe = recipes.get(slot);
×
267
                    if (newRecipe != null) {
×
268
                        craftingNetwork.addCraftingInterfaceRecipe(channel, state, newRecipe);
×
269
                    }
270
                }
×
271
            }
272
            slots.clear();
×
273
        }
274
    }
×
275

276
    @Nullable
277
    protected static <T, M> IngredientInstanceWrapper<T, M> insertIntoNetwork(IngredientInstanceWrapper<T, M> wrapper,
278
                                                                              INetwork network, int channel) {
279
        IPositionedAddonsNetworkIngredients<T, M> storageNetwork = wrapper.getComponent()
×
280
                .getCapability(PositionedAddonsNetworkIngredientsHandlerConfig.CAPABILITY)
×
281
                .map(n -> (IPositionedAddonsNetworkIngredients<T, M>) n.getStorage(network).orElse(null))
×
282
                .orElse(null);
×
283
        if (storageNetwork != null) {
×
284
            IIngredientComponentStorage<T, M> storage = storageNetwork.getChannel(channel);
×
285
            T remaining = storage.insert(wrapper.getInstance(), false);
×
286
            if (wrapper.getComponent().getMatcher().isEmpty(remaining)) {
×
287
                return null;
×
288
            } else {
289
                return new IngredientInstanceWrapper<>(wrapper.getComponent(), remaining);
×
290
            }
291
        }
292
        return wrapper;
×
293
    }
294

295
    @Override
296
    public void addDrops(PartTarget target, State state, List<ItemStack> itemStacks, boolean dropMainElement, boolean saveState) {
297
        // Drop any remaining output ingredients (only items)
298
        for (IngredientInstanceWrapper<?, ?> ingredientInstanceWrapper : state.getInventoryOutputBuffer()) {
×
299
            if (ingredientInstanceWrapper.getComponent() == IngredientComponent.ITEMSTACK) {
×
300
                itemStacks.add((ItemStack) ingredientInstanceWrapper.getInstance());
×
301
            }
302
        }
×
303
        state.getInventoryOutputBuffer().clear();
×
304

305
        // Drop the stored variables
306
        for(int i = 0; i < state.getInventoryVariables().getContainerSize(); i++) {
×
307
            ItemStack itemStack = state.getInventoryVariables().getItem(i);
×
308
            if(!itemStack.isEmpty()) {
×
309
                itemStacks.add(itemStack);
×
310
            }
311
        }
312
        state.getInventoryVariables().clearContent();
×
313

314
        super.addDrops(target, state, itemStacks, dropMainElement, saveState);
×
315
    }
×
316

317
    public static class State extends PartStateBase<PartTypeInterfaceCrafting>
318
            implements ICraftingInterface, ICraftingResultsSink {
319

320
        private final CraftingJobHandler craftingJobHandler;
321
        private final SimpleInventory inventoryVariables;
322
        private final List<InventoryVariableEvaluator<ValueObjectTypeRecipe.ValueRecipe>> variableEvaluators;
323
        private final List<IngredientInstanceWrapper<?, ?>> inventoryOutputBuffer;
324
        private final Int2ObjectMap<MutableComponent> recipeSlotMessages;
325
        private final Int2BooleanMap recipeSlotValidated;
326
        private final IntSet delayedRecipeReloads;
327
        private final Map<IVariable, Boolean> variableListeners;
328
        private int channelCrafting = 0;
×
329
        private boolean disableCraftingCheck = false;
×
330

331
        private final Int2ObjectMap<IRecipeDefinition> currentRecipes;
332
        private PartTarget target = null;
×
333
        private INetwork network = null;
×
334
        private ICraftingNetwork craftingNetwork = null;
×
335
        private IPartNetwork partNetwork = null;
×
336
        private int channel = -1;
×
337
        private boolean shouldAddToCraftingNetwork = false;
×
338
        private Player lastPlayer;
339

340
        public State() {
×
341
            this.craftingJobHandler = new CraftingJobHandler(1, true,
×
342
                    CraftingProcessOverrides.REGISTRY.getCraftingProcessOverrides(), this);
×
343
            this.inventoryVariables = new SimpleInventory(9, 1);
×
344
            this.inventoryVariables.addDirtyMarkListener(this);
×
345
            this.variableEvaluators = Lists.newArrayList();
×
346
            this.inventoryOutputBuffer = Lists.newArrayList();
×
347
            this.recipeSlotMessages = new Int2ObjectArrayMap<>();
×
348
            this.recipeSlotValidated = new Int2BooleanArrayMap();
×
349
            this.delayedRecipeReloads = new IntArraySet();
×
350
            this.variableListeners = new MapMaker().weakKeys().makeMap();
×
351
            this.currentRecipes = new Int2ObjectArrayMap<>();
×
352
        }
×
353

354
        @Override
355
        protected int getDefaultUpdateInterval() {
356
            return GeneralConfig.minCraftingInterfaceUpdateFreq;
×
357
        }
358

359
        /**
360
         * @return The inner variables inventory
361
         */
362
        public SimpleInventory getInventoryVariables() {
363
            return this.inventoryVariables;
×
364
        }
365

366
        @Override
367
        public void writeToNBT(CompoundTag tag) {
368
            super.writeToNBT(tag);
×
369
            inventoryVariables.writeToNBT(tag, "variables");
×
370

371
            ListTag instanceTags = new ListTag();
×
372
            for (IngredientInstanceWrapper instanceWrapper : inventoryOutputBuffer) {
×
373
                CompoundTag instanceTag = new CompoundTag();
×
374
                instanceTag.putString("component", instanceWrapper.getComponent().getRegistryName().toString());
×
375
                instanceTag.put("instance", instanceWrapper.getComponent().getSerializer().serializeInstance(instanceWrapper.getInstance()));
×
376
                instanceTags.add(instanceTag);
×
377
            }
×
378
            tag.put("inventoryOutputBuffer", instanceTags);
×
379

380
            this.craftingJobHandler.writeToNBT(tag);
×
381
            tag.putInt("channelCrafting", channelCrafting);
×
382

383
            CompoundTag recipeSlotErrorsTag = new CompoundTag();
×
384
            for (Int2ObjectMap.Entry<MutableComponent> entry : this.recipeSlotMessages.int2ObjectEntrySet()) {
×
385
                NBTClassType.writeNbt(MutableComponent.class, String.valueOf(entry.getIntKey()), entry.getValue(), recipeSlotErrorsTag);
×
386
            }
×
387
            tag.put("recipeSlotMessages", recipeSlotErrorsTag);
×
388

389
            CompoundTag recipeSlotValidatedTag = new CompoundTag();
×
390
            for (Int2BooleanMap.Entry entry : this.recipeSlotValidated.int2BooleanEntrySet()) {
×
391
                recipeSlotValidatedTag.putBoolean(String.valueOf(entry.getIntKey()), entry.getBooleanValue());
×
392
            }
×
393
            tag.put("recipeSlotValidated", recipeSlotValidatedTag);
×
394

395
            tag.putBoolean("disableCraftingCheck", disableCraftingCheck);
×
396
        }
×
397

398
        @Override
399
        public void readFromNBT(CompoundTag tag) {
400
            super.readFromNBT(tag);
×
401
            inventoryVariables.readFromNBT(tag, "variables");
×
402

403
            this.inventoryOutputBuffer.clear();
×
404
            for (Tag instanceTagRaw : tag.getList("inventoryOutputBuffer", Tag.TAG_COMPOUND)) {
×
405
                CompoundTag instanceTag = (CompoundTag) instanceTagRaw;
×
406
                String componentName = instanceTag.getString("component");
×
407
                IngredientComponent<?, ?> component = IngredientComponent.REGISTRY.getValue(new ResourceLocation(componentName));
×
408
                this.inventoryOutputBuffer.add(new IngredientInstanceWrapper(component,
×
409
                        component.getSerializer().deserializeInstance(instanceTag.get("instance"))));
×
410
            }
×
411

412
            this.craftingJobHandler.readFromNBT(tag);
×
413
            this.channelCrafting = tag.getInt("channelCrafting");
×
414

415
            this.recipeSlotMessages.clear();
×
416
            CompoundTag recipeSlotErrorsTag = tag.getCompound("recipeSlotMessages");
×
417
            for (String slot : recipeSlotErrorsTag.getAllKeys()) {
×
418
                MutableComponent unlocalizedString = NBTClassType.readNbt(MutableComponent.class, slot, recipeSlotErrorsTag);
×
419
                this.recipeSlotMessages.put(Integer.parseInt(slot), unlocalizedString);
×
420
            }
×
421

422
            this.recipeSlotValidated.clear();
×
423
            CompoundTag recipeSlotValidatedTag = tag.getCompound("recipeSlotValidated");
×
424
            for (String slot : recipeSlotValidatedTag.getAllKeys()) {
×
425
                this.recipeSlotValidated.put(Integer.parseInt(slot), recipeSlotValidatedTag.getBoolean(slot));
×
426
            }
×
427

428
            this.disableCraftingCheck = tag.getBoolean("disableCraftingCheck");
×
429
        }
×
430

431
        public void setChannelCrafting(int channelCrafting) {
432
            if (this.channelCrafting != channelCrafting) {
×
433
                // Unregister from the network
434
                if (craftingNetwork != null) {
×
435
                    craftingNetwork.removeCraftingInterface(this.channelCrafting, this);
×
436
                }
437

438
                // Update the channel
439
                this.channelCrafting = channelCrafting;
×
440

441
                // Re-register to the network
442
                if (craftingNetwork != null) {
×
443
                    craftingNetwork.addCraftingInterface(this.channelCrafting, this);
×
444
                }
445

446
                sendUpdate();
×
447
            }
448
        }
×
449

450
        public int getChannelCrafting() {
451
            return channelCrafting;
×
452
        }
453

454
        public void reloadRecipes() {
455
            this.currentRecipes.clear();
×
456
            this.recipeSlotMessages.clear();
×
457
            this.recipeSlotValidated.clear();
×
458
            variableEvaluators.clear();
×
459
            for (int i = 0; i < getInventoryVariables().getContainerSize(); i++) {
×
460
                int slot = i;
×
461
                variableEvaluators.add(new InventoryVariableEvaluator<ValueObjectTypeRecipe.ValueRecipe>(
×
462
                        getInventoryVariables(), slot, ValueTypes.OBJECT_RECIPE) {
×
463
                    @Override
464
                    public void onErrorsChanged() {
465
                        super.onErrorsChanged();
×
466
                        setLocalErrors(slot, getErrors());
×
467
                    }
×
468
                });
469
            }
470
            if (this.partNetwork != null) {
×
471
                for (int i = 0; i < getInventoryVariables().getContainerSize(); i++) {
×
472
                    reloadRecipe(i);
×
473
                }
474
            }
475
        }
×
476

477
        private void setLocalErrors(int slot, List<MutableComponent> errors) {
478
            if (errors.isEmpty()) {
×
479
                if (this.recipeSlotMessages.size() > slot) {
×
480
                    this.recipeSlotMessages.remove(slot);
×
481
                }
482
            } else {
483
                this.recipeSlotMessages.put(slot, errors.get(0));
×
484
            }
485
        }
×
486

487
        protected void reloadRecipe(int slot) {
488
            this.currentRecipes.remove(slot);
×
489
            if (this.recipeSlotMessages.size() > slot) {
×
490
                this.recipeSlotMessages.remove(slot);
×
491
            }
492
            if (this.recipeSlotValidated.size() > slot) {
×
493
                this.recipeSlotValidated.remove(slot);
×
494
            }
495
            if (this.partNetwork != null) {
×
496
                InventoryVariableEvaluator<ValueObjectTypeRecipe.ValueRecipe> evaluator = variableEvaluators.get(slot);
×
497
                evaluator.refreshVariable(network, false);
×
498
                IVariable<ValueObjectTypeRecipe.ValueRecipe> variable = evaluator.getVariable(network);
×
499
                if (variable != null) {
×
500
                    try {
501
                        // Refresh the recipe if variable is changed
502
                        // The map is needed because we only want to register the listener once for each variable
503
                        if (!this.variableListeners.containsKey(variable)) {
×
504
                            variable.addInvalidationListener(() -> {
×
505
                                this.variableListeners.remove(variable);
×
506
                                delayedReloadRecipe(slot);
×
507
                            });
×
508
                            this.variableListeners.put(variable, true);
×
509
                        }
510

511
                        IValue value = variable.getValue();
×
512
                        if (value.getType() == ValueTypes.OBJECT_RECIPE) {
×
513
                            Optional<IRecipeDefinition> recipeWrapper = ((ValueObjectTypeRecipe.ValueRecipe) value).getRawValue();
×
514
                            if (recipeWrapper.isPresent()) {
×
515
                                IRecipeDefinition recipe = recipeWrapper.get();
×
516
                                if (!GeneralConfig.validateRecipesCraftingInterface || this.disableCraftingCheck || isValid(recipe)) {
×
517
                                    this.currentRecipes.put(slot, recipe);
×
518
                                    this.recipeSlotValidated.put(slot, true);
×
519
                                    this.recipeSlotMessages.put(slot, new TranslatableComponent("gui.integratedcrafting.partinterface.slot.message.valid"));
×
520
                                } else {
521
                                    this.recipeSlotMessages.put(slot, new TranslatableComponent("gui.integratedcrafting.partinterface.slot.message.invalid"));
×
522
                                }
523
                            }
524
                        } else {
×
525
                            this.recipeSlotMessages.put(slot, new TranslatableComponent("gui.integratedcrafting.partinterface.slot.message.norecipe"));
×
526
                        }
527
                    } catch (EvaluationException e) {
×
528
                        this.recipeSlotMessages.put(slot, e.getErrorMessage());
×
529
                    }
×
530
                } else {
531
                    this.recipeSlotMessages.put(slot, new TranslatableComponent("gui.integratedcrafting.partinterface.slot.message.norecipe"));
×
532
                }
533

534
                try {
535
                    IPartNetwork partNetwork = NetworkHelpers.getPartNetworkChecked(network);
×
536
                    MinecraftForge.EVENT_BUS.post(new PartVariableDrivenVariableContentsUpdatedEvent<>(network,
×
537
                            partNetwork, getTarget(),
×
538
                            PartTypes.INTERFACE_CRAFTING, this, lastPlayer, variable,
539
                            variable != null ? variable.getValue() : null));
×
540
                } catch (EvaluationException e) {
×
541
                    // Ignore error
542
                }
×
543
            }
544
            sendUpdate();
×
545
        }
×
546

547
        public void setLastPlayer(Player lastPlayer) {
548
            this.lastPlayer = lastPlayer;
×
549
        }
×
550

551
        private void delayedReloadRecipe(int slot) {
552
            this.delayedRecipeReloads.add(slot);
×
553
        }
×
554

555

556
        private boolean isValid(IRecipeDefinition recipe) {
557
            DimPos dimPos = getTarget().getTarget().getPos();
×
558
            Direction side = getTarget().getTarget().getSide();
×
559
            IRecipeHandler recipeHandler = BlockEntityHelpers.getCapability(dimPos.getLevel(true), dimPos.getBlockPos(), side, Capabilities.RECIPE_HANDLER).orElse(null);
×
560
            if (recipeHandler == null) {
×
561
                BlockState blockState = dimPos.getLevel(true).getBlockState(dimPos.getBlockPos());
×
562
                recipeHandler = BlockCapabilities.getInstance().getCapability(blockState, Capabilities.RECIPE_HANDLER,
×
563
                        dimPos.getLevel(true), dimPos.getBlockPos(), side)
×
564
                .orElse(null);
×
565
            }
566
            if (recipeHandler != null) {
×
567
                IMixedIngredients simulatedOutput = recipeHandler.simulate(MixedIngredients.fromRecipeInput(recipe));
×
568
                if (simulatedOutput != null && !simulatedOutput.isEmpty()) {
×
569
                    if (recipe.getOutput().containsAll(simulatedOutput)) {
×
570
                        return true;
×
571
                    } else {
572
                        if (GeneralConfig.logRecipeValidationFailures) {
×
573
                            IntegratedCrafting.clog(Level.INFO, "Recipe validation failure: incompatible recipe output and simulated output:\nRecipe output: " + recipe.getOutput() + "\nSimulated output: " + simulatedOutput);
×
574
                        }
575
                        return false;
×
576
                    }
577
                }
578
                if (GeneralConfig.logRecipeValidationFailures) {
×
579
                    IntegratedCrafting.clog(Level.INFO, "Recipe validation failure: No output was obtained when simulating a recipe\n" + recipe);
×
580
                }
581
                return false;
×
582
            }
583
            return true; // No recipe handler capability is present, so we can't confirm that the recipe will work.
×
584
        }
585

586
        @Override
587
        public void onDirty() {
588
            super.onDirty();
×
589

590
            // Unregister from the network, when all old recipes are still in place
591
            if (craftingNetwork != null) {
×
592
                craftingNetwork.removeCraftingInterface(channelCrafting, this);
×
593
            }
594

595
            // Recalculate recipes
596
            if (getTarget() != null && !getTarget().getCenter().getPos().getLevel(true).isClientSide) {
×
597
                reloadRecipes();
×
598
            }
599

600
            // Re-register to the network, to force an update for all new recipes
601
            if (craftingNetwork != null) {
×
602
                craftingNetwork.addCraftingInterface(channelCrafting, this);
×
603
            }
604
        }
×
605

606
        public void setTarget(PartTarget target) {
607
            this.target = target;
×
608
        }
×
609

610
        public PartTarget getTarget() {
611
            return target;
×
612
        }
613

614
        public void setNetworks(@Nullable INetwork network, @Nullable ICraftingNetwork craftingNetwork,
615
                                @Nullable IPartNetwork partNetwork, int channel) {
616
            this.network = network;
×
617
            this.craftingNetwork = craftingNetwork;
×
618
            this.partNetwork = partNetwork;
×
619
            this.channel = channel;
×
620
            reloadRecipes();
×
621
            if (network != null) {
×
622
                this.getCraftingJobHandler().reRegisterObservers(network);
×
623
            }
624
        }
×
625

626
        public ICraftingNetwork getCraftingNetwork() {
627
            return craftingNetwork;
×
628
        }
629

630
        @Override
631
        public int getChannel() {
632
            return channel;
×
633
        }
634

635
        @Override
636
        public Collection<IRecipeDefinition> getRecipes() {
637
            return this.currentRecipes.values();
×
638
        }
639

640
        public Int2ObjectMap<IRecipeDefinition> getRecipesIndexed() {
641
            return currentRecipes;
×
642
        }
643

644
        @Override
645
        public boolean canScheduleCraftingJobs() {
646
            return getCraftingJobHandler().canScheduleCraftingJobs();
×
647
        }
648

649
        @Override
650
        public void scheduleCraftingJob(CraftingJob craftingJob) {
651
            getCraftingJobHandler().scheduleCraftingJob(craftingJob);
×
652
        }
×
653

654
        @Override
655
        public int getCraftingJobsCount() {
656
            return this.craftingJobHandler.getAllCraftingJobs().size();
×
657
        }
658

659
        @Override
660
        public Iterator<CraftingJob> getCraftingJobs() {
661
            return this.craftingJobHandler.getAllCraftingJobs().values().iterator();
×
662
        }
663

664
        @Override
665
        public List<Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>>> getPendingCraftingJobOutputs(int craftingJobId) {
666
            List<Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>>> pending = this.craftingJobHandler.getProcessingCraftingJobsPendingIngredients().get(craftingJobId);
×
667
            if (pending == null) {
×
668
                pending = Lists.newArrayList();
×
669
            }
670
            return pending;
×
671
        }
672

673
        @Override
674
        public CraftingJobStatus getCraftingJobStatus(ICraftingNetwork network, int channel, int craftingJobId) {
675
            return craftingJobHandler.getCraftingJobStatus(network, channel, craftingJobId);
×
676
        }
677

678
        @Override
679
        public void cancelCraftingJob(int channel, int craftingJobId) {
680
            craftingJobHandler.markCraftingJobFinished(craftingJobId);
×
681
        }
×
682

683
        @Override
684
        public PrioritizedPartPos getPosition() {
685
            return PrioritizedPartPos.of(getTarget().getCenter(), getPriority());
×
686
        }
687

688
        public CraftingJobHandler getCraftingJobHandler() {
689
            return craftingJobHandler;
×
690
        }
691

692
        public boolean shouldAddToCraftingNetwork() {
693
            return shouldAddToCraftingNetwork;
×
694
        }
695

696
        public void setShouldAddToCraftingNetwork(boolean shouldAddToCraftingNetwork) {
697
            this.shouldAddToCraftingNetwork = shouldAddToCraftingNetwork;
×
698
        }
×
699

700
        public List<IngredientInstanceWrapper<?, ?>> getInventoryOutputBuffer() {
701
            return inventoryOutputBuffer;
×
702
        }
703

704
        @Override
705
        public <T> LazyOptional<T> getCapability(Capability<T> capability, INetwork network, IPartNetwork partNetwork, PartTarget target) {
706
            if (capability == CraftingInterfaceConfig.CAPABILITY) {
×
707
                return LazyOptional.of(() -> this).cast();
×
708
            }
709

710
            // Expose the whole storage
711
            if (this.network != null) {
×
712
                IngredientComponent<?, ?> ingredientComponent = IngredientComponent.getIngredientComponentForStorageCapability(capability);
×
713
                if (ingredientComponent != null) {
×
714
                    T cap = wrapStorageCapability(capability, ingredientComponent);
×
715
                    if (cap != null) {
×
716
                        return LazyOptional.of(() -> cap);
×
717
                    }
718
                }
719
            }
720

721
            return super.getCapability(capability, network, partNetwork, target);
×
722
        }
723

724
        protected <C, T, M> C wrapStorageCapability(Capability<C> capability, IngredientComponent<T, M> ingredientComponent) {
725
            IIngredientComponentStorage<T, M> storage = CraftingHelpers.getNetworkStorage(this.network, this.channelCrafting,
×
726
                    ingredientComponent, false);
727

728
            // Don't allow extraction, only insertion
729
            storage = new IngredientComponentStorageSlottedInsertProxy<>(storage);
×
730

731
            return ingredientComponent.getStorageWrapperHandler(capability).wrapStorage(storage);
×
732
        }
733

734
        @Override
735
        public <T, M> void addResult(IngredientComponent<T, M> ingredientComponent, T instance) {
736
            this.getInventoryOutputBuffer().add(new IngredientInstanceWrapper<>(ingredientComponent, instance));
×
737

738
            // Try to flush buffer immediately
739
            if (this.network != null) {
×
740
                this.flushInventoryOutputBuffer(this.network);
×
741
            }
742
        }
×
743

744
        public void setIngredientComponentTargetSideOverride(IngredientComponent<?, ?> ingredientComponent, Direction side) {
745
            if (getTarget().getTarget().getSide() == side) {
×
746
                craftingJobHandler.setIngredientComponentTarget(ingredientComponent, null);
×
747
            } else {
748
                craftingJobHandler.setIngredientComponentTarget(ingredientComponent, side);
×
749
            }
750
            sendUpdate();
×
751
        }
×
752

753
        public Direction getIngredientComponentTargetSideOverride(IngredientComponent<?, ?> ingredientComponent) {
754
            Direction side = craftingJobHandler.getIngredientComponentTarget(ingredientComponent);
×
755
            if (side == null) {
×
756
                side = getTarget().getTarget().getSide();
×
757
            }
758
            return side;
×
759
        }
760

761
        public boolean isRecipeSlotValid(int slot) {
762
            return this.recipeSlotValidated.containsKey(slot);
×
763
        }
764

765
        @Nullable
766
        public MutableComponent getRecipeSlotUnlocalizedMessage(int slot) {
767
            return this.recipeSlotMessages.get(slot);
×
768
        }
769

770
        public IntSet getDelayedRecipeReloads() {
771
            return delayedRecipeReloads;
×
772
        }
773

774
        public void setDisableCraftingCheck(boolean disableCraftingCheck) {
775
            if (disableCraftingCheck != this.disableCraftingCheck) {
×
776
                this.disableCraftingCheck = disableCraftingCheck;
×
777

778
                this.sendUpdate();
×
779
            }
780
        }
×
781

782
        public boolean isDisableCraftingCheck() {
783
            return disableCraftingCheck;
×
784
        }
785

786
        public void flushInventoryOutputBuffer(INetwork network) {
787
            // Try to insert each ingredient in the buffer into the network.
788
            boolean changed = false;
×
789
            ListIterator<IngredientInstanceWrapper<?, ?>> outputBufferIt = this.getInventoryOutputBuffer().listIterator();
×
790
            while (outputBufferIt.hasNext()) {
×
791
                IngredientInstanceWrapper<?, ?> oldWrapper = outputBufferIt.next();
×
792

793
                // Force observation before insertion (see #98 on why this is necessary)
794
                this.forceObservationOnInsertable(oldWrapper);
×
795

796
                IngredientInstanceWrapper<?, ?> newWrapper = insertIntoNetwork(oldWrapper,
×
797
                        network, this.getChannelCrafting());
×
798
                if (newWrapper != oldWrapper) {
×
799
                    changed = true;
×
800
                }
801
                if (newWrapper == null) {
×
802
                    outputBufferIt.remove();
×
803
                } else {
804
                    outputBufferIt.set(newWrapper);
×
805
                }
806
            }
×
807

808
            // If at least one ingredient was inserted, force a sync observer update in the network.
809
            if (changed) {
×
810
                CraftingHelpers.beforeCalculateCraftingJobs(network, getChannelCrafting());
×
811
            }
812
        }
×
813

814
        /**
815
         * Iterate over all positions that *could* accept the given instance,
816
         * and force an observation over them.
817
         *
818
         * This is necessary to ensure that we have the latest state indexed right before insertion.
819
         * This allows us to force another observation right after the insertion,
820
         * which will guarantee that we will track the expected diff events as result.
821
         *
822
         * @param oldWrapper The ingredient to attempt to insert (simulated).
823
         * @param <T> Ingredient type.
824
         * @param <M> Match flags.
825
         */
826
        protected <T, M> void forceObservationOnInsertable(IngredientInstanceWrapper<T, M> oldWrapper) {
827
            IIngredientMatcher<T, M> matcher = oldWrapper.getComponent().getMatcher();
×
828
            IPositionedAddonsNetworkIngredients<T, M> ingredientsNetwork = CraftingHelpers.getIngredientsNetwork(network, oldWrapper.getComponent()).orElse(null);
×
829
            if (ingredientsNetwork != null) {
×
830
                boolean marked = false;
×
831
                INetworkIngredientsChannel<?, ?> ingredientsNetworkChannel = ingredientsNetwork.getChannel(this.getChannelCrafting());
×
832
                T instance = oldWrapper.getInstance();
×
833
                for (PartPos position : ingredientsNetworkChannel.findNonFullPositions()) {
×
834
                    T instanceOut = ingredientsNetwork.getPositionedStorage(position).insert(instance, true);
×
835
                    if (!matcher.matchesExactly(instanceOut, instance)) {
×
836
                        marked = true;
×
837
                        instance = instanceOut;
×
838
                        ingredientsNetwork.scheduleObservationForced(this.getChannelCrafting(), position);
×
839
                        if (matcher.isEmpty(instance)) {
×
840
                            break;
×
841
                        }
842
                    }
843
                }
×
844

845
                if (marked || ingredientsNetwork.isObservationForcedPending(channel)) {
×
846
                    ingredientsNetwork.runObserverSync();
×
847
                }
848
            }
849
        }
×
850
    }
851
}
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