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

CyclopsMC / IntegratedCrafting / #479011800

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

push

github

rubensworks
Add Attuned Crafting Interface

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

113 existing lines in 4 files now uncovered.

750 of 3015 relevant lines covered (24.88%)

0.25 hits per line

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

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.*;
6
import net.minecraft.core.Direction;
7
import net.minecraft.nbt.CompoundTag;
8
import net.minecraft.network.FriendlyByteBuf;
9
import net.minecraft.network.chat.Component;
10
import net.minecraft.network.chat.MutableComponent;
11
import net.minecraft.server.level.ServerPlayer;
12
import net.minecraft.world.MenuProvider;
13
import net.minecraft.world.SimpleContainer;
14
import net.minecraft.world.entity.player.Inventory;
15
import net.minecraft.world.entity.player.Player;
16
import net.minecraft.world.inventory.AbstractContainerMenu;
17
import net.minecraft.world.item.ItemStack;
18
import net.minecraft.world.level.block.state.BlockState;
19
import net.minecraftforge.common.MinecraftForge;
20
import org.apache.commons.lang3.tuple.Triple;
21
import org.apache.logging.log4j.Level;
22
import org.cyclops.commoncapabilities.api.capability.block.BlockCapabilities;
23
import org.cyclops.commoncapabilities.api.capability.recipehandler.IRecipeDefinition;
24
import org.cyclops.commoncapabilities.api.capability.recipehandler.IRecipeHandler;
25
import org.cyclops.commoncapabilities.api.ingredient.IMixedIngredients;
26
import org.cyclops.cyclopscore.datastructure.DimPos;
27
import org.cyclops.cyclopscore.helper.BlockEntityHelpers;
28
import org.cyclops.cyclopscore.inventory.SimpleInventory;
29
import org.cyclops.cyclopscore.persist.nbt.NBTClassType;
30
import org.cyclops.integratedcrafting.Capabilities;
31
import org.cyclops.integratedcrafting.GeneralConfig;
32
import org.cyclops.integratedcrafting.IntegratedCrafting;
33
import org.cyclops.integratedcrafting.api.network.ICraftingNetwork;
34
import org.cyclops.integratedcrafting.core.part.PartTypeInterfaceCraftingBase;
35
import org.cyclops.integratedcrafting.inventory.container.ContainerPartInterfaceCrafting;
36
import org.cyclops.integratedcrafting.inventory.container.ContainerPartInterfaceCraftingSettings;
37
import org.cyclops.integrateddynamics.api.evaluate.EvaluationException;
38
import org.cyclops.integrateddynamics.api.evaluate.variable.IValue;
39
import org.cyclops.integrateddynamics.api.evaluate.variable.IVariable;
40
import org.cyclops.integrateddynamics.api.evaluate.variable.ValueDeseralizationContext;
41
import org.cyclops.integrateddynamics.api.network.INetwork;
42
import org.cyclops.integrateddynamics.api.network.IPartNetwork;
43
import org.cyclops.integrateddynamics.api.part.IPartContainer;
44
import org.cyclops.integrateddynamics.api.part.PartPos;
45
import org.cyclops.integrateddynamics.api.part.PartTarget;
46
import org.cyclops.integrateddynamics.core.evaluate.InventoryVariableEvaluator;
47
import org.cyclops.integrateddynamics.core.evaluate.variable.ValueObjectTypeRecipe;
48
import org.cyclops.integrateddynamics.core.evaluate.variable.ValueTypes;
49
import org.cyclops.integrateddynamics.core.helper.NetworkHelpers;
50
import org.cyclops.integrateddynamics.core.helper.PartHelpers;
51
import org.cyclops.integrateddynamics.core.part.PartTypeBase;
52
import org.cyclops.integrateddynamics.core.part.event.PartVariableDrivenVariableContentsUpdatedEvent;
53

54
import javax.annotation.Nullable;
55
import java.util.Collection;
56
import java.util.List;
57
import java.util.Map;
58
import java.util.Optional;
59

60
/**
61
 * Interface for auto crafting.
62
 * @author rubensworks
63
 */
64
public class PartTypeInterfaceCrafting extends PartTypeInterfaceCraftingBase<PartTypeInterfaceCrafting, PartTypeInterfaceCrafting.State> {
65

66
    public PartTypeInterfaceCrafting(String name) {
67
        super(name);
×
68
    }
×
69

70
    @Override
71
    public int getConsumptionRate(State state) {
72
        return state.getCraftingJobHandler().getProcessingCraftingJobs().size() * GeneralConfig.interfaceCraftingBaseConsumption;
×
73
    }
74

75
    @Override
76
    public Optional<MenuProvider> getContainerProvider(PartPos pos) {
77
        return Optional.of(new MenuProvider() {
×
78

79
            @Override
80
            public MutableComponent getDisplayName() {
81
                return Component.translatable(getTranslationKey());
×
82
            }
83

84
            @Override
85
            public AbstractContainerMenu createMenu(int id, Inventory playerInventory, Player playerEntity) {
86
                Triple<IPartContainer, PartTypeBase, PartTarget> data = PartHelpers.getContainerPartConstructionData(pos);
×
87
                PartTypeInterfaceCrafting.State partState = (PartTypeInterfaceCrafting.State) data.getLeft().getPartState(data.getRight().getCenter().getSide());
×
88
                return new ContainerPartInterfaceCrafting(id, playerInventory, partState.getInventoryVariables(),
×
89
                        Optional.of(data.getRight()), Optional.of(data.getLeft()), (PartTypeInterfaceCrafting) data.getMiddle());
×
90
            }
91
        });
92
    }
93

94
    @Override
95
    public void writeExtraGuiData(FriendlyByteBuf packetBuffer, PartPos pos, ServerPlayer player) {
96
        // Write inventory size
97
        IPartContainer partContainer = PartHelpers.getPartContainerChecked(pos);
×
98
        PartTypeInterfaceCrafting.State partState = (PartTypeInterfaceCrafting.State) partContainer.getPartState(pos.getSide());
×
99
        packetBuffer.writeInt(partState.getInventoryVariables().getContainerSize());
×
100

101
        super.writeExtraGuiData(packetBuffer, pos, player);
×
102
    }
×
103

104
    @Override
105
    public Optional<MenuProvider> getContainerProviderSettings(PartPos pos) {
106
        return Optional.of(new MenuProvider() {
×
107

108
            @Override
109
            public MutableComponent getDisplayName() {
110
                return Component.translatable(getTranslationKey());
×
111
            }
112

113
            @Override
114
            public AbstractContainerMenu createMenu(int id, Inventory playerInventory, Player playerEntity) {
115
                Triple<IPartContainer, PartTypeBase, PartTarget> data = PartHelpers.getContainerPartConstructionData(pos);
×
116
                return new ContainerPartInterfaceCraftingSettings(id, playerInventory, new SimpleContainer(0),
×
117
                        data.getRight(), Optional.of(data.getLeft()), data.getMiddle());
×
118
            }
119
        });
120
    }
121

122
    @Override
123
    protected PartTypeInterfaceCrafting.State constructDefaultState() {
124
        return new PartTypeInterfaceCrafting.State();
×
125
    }
126

127
    @Override
128
    public void update(INetwork network, IPartNetwork partNetwork, PartTarget target, State state) {
129
        super.update(network, partNetwork, target, state);
×
130

131
        // Reload recipes if needed
UNCOV
132
        IntSet slots = state.getDelayedRecipeReloads();
×
133
        if (!slots.isEmpty()) {
×
134
            ICraftingNetwork craftingNetwork = network.getCapability(getNetworkCapability()).orElse(null);
×
135
            if (craftingNetwork != null) {
×
136
                IntSet slotsCopy = new IntOpenHashSet(slots); // Create a copy, to allow insertion into slots during this loop
×
137
                slots.clear();
×
NEW
138
                int channel = state.getChannelCrafting();
×
UNCOV
139
                for (Integer slot : slotsCopy) {
×
140
                    // Remove the old recipe from the network
141
                    Int2ObjectMap<IRecipeDefinition> recipes = state.getRecipesIndexed();
×
142
                    IRecipeDefinition oldRecipe = recipes.get(slot);
×
143
                    if (oldRecipe != null) {
×
144
                        craftingNetwork.removeCraftingInterfaceRecipe(channel, state, oldRecipe);
×
145
                    }
146

147
                    // Reload the recipe in the slot
148
                    // We simulate initialization for the first two ticks, as dependency variables may still be loading,
149
                    // and errored may only go away after these dependencies are fully loaded.
150
                    // Related to CyclopsMC/IntegratedCrafting#110
151
                    state.reloadRecipe(slot, state.ticksAfterReload <= 1);
×
152

153
                    // Add the new recipe to the network
154
                    IRecipeDefinition newRecipe = recipes.get(slot);
×
155
                    if (newRecipe != null) {
×
156
                        craftingNetwork.addCraftingInterfaceRecipe(channel, state, newRecipe);
×
157
                    }
158
                }
×
159
            }
160
        }
161

162
        // Internal tick counter
163
        state.ticksAfterReload++;
×
164
    }
×
165

166
    @Override
167
    public void addDrops(PartTarget target, State state, List<ItemStack> itemStacks, boolean dropMainElement, boolean saveState) {
168
        // Drop the stored variables
169
        for(int i = 0; i < state.getInventoryVariables().getContainerSize(); i++) {
×
170
            ItemStack itemStack = state.getInventoryVariables().getItem(i);
×
171
            if(!itemStack.isEmpty()) {
×
172
                itemStacks.add(itemStack);
×
173
            }
174
        }
175
        state.getInventoryVariables().clearContent();
×
176

177
        super.addDrops(target, state, itemStacks, dropMainElement, saveState);
×
178
    }
×
179

180
    public static class State extends PartTypeInterfaceCraftingBase.State<PartTypeInterfaceCrafting, PartTypeInterfaceCrafting.State> {
181

182
        protected int ticksAfterReload = 0;
×
183

184
        private final SimpleInventory inventoryVariables;
185
        private final List<InventoryVariableEvaluator<ValueObjectTypeRecipe.ValueRecipe>> variableEvaluators;
186
        private final Int2ObjectMap<MutableComponent> recipeSlotMessages;
187
        private final Int2BooleanMap recipeSlotValidated;
188
        private final IntSet delayedRecipeReloads;
189
        private final Map<IVariable, Boolean> variableListeners;
190
        private boolean disableCraftingCheck = false;
×
191

192
        private final Int2ObjectMap<IRecipeDefinition> currentRecipes;
193

194
        public State() {
×
195
            this.inventoryVariables = new SimpleInventory(9, 1);
×
196
            this.inventoryVariables.addDirtyMarkListener(this);
×
197
            this.variableEvaluators = Lists.newArrayList();
×
198
            this.recipeSlotMessages = new Int2ObjectArrayMap<>();
×
199
            this.recipeSlotValidated = new Int2BooleanArrayMap();
×
200
            this.delayedRecipeReloads = new IntArraySet();
×
201
            this.variableListeners = new MapMaker().weakKeys().makeMap();
×
202
            this.currentRecipes = new Int2ObjectArrayMap<>();
×
203
        }
×
204

205
        /**
206
         * @return The inner variables inventory
207
         */
208
        public SimpleInventory getInventoryVariables() {
209
            return this.inventoryVariables;
×
210
        }
211

212
        @Override
213
        public void writeToNBT(CompoundTag tag) {
214
            super.writeToNBT(tag);
×
215
            inventoryVariables.writeToNBT(tag, "variables");
×
216

217
            CompoundTag recipeSlotErrorsTag = new CompoundTag();
×
218
            for (Int2ObjectMap.Entry<MutableComponent> entry : this.recipeSlotMessages.int2ObjectEntrySet()) {
×
219
                NBTClassType.writeNbt(MutableComponent.class, String.valueOf(entry.getIntKey()), entry.getValue(), recipeSlotErrorsTag);
×
220
            }
×
221
            tag.put("recipeSlotMessages", recipeSlotErrorsTag);
×
222

223
            CompoundTag recipeSlotValidatedTag = new CompoundTag();
×
224
            for (Int2BooleanMap.Entry entry : this.recipeSlotValidated.int2BooleanEntrySet()) {
×
225
                recipeSlotValidatedTag.putBoolean(String.valueOf(entry.getIntKey()), entry.getBooleanValue());
×
226
            }
×
227
            tag.put("recipeSlotValidated", recipeSlotValidatedTag);
×
228

229
            tag.putBoolean("disableCraftingCheck", disableCraftingCheck);
×
230
        }
×
231

232
        @Override
233
        public void readFromNBT(ValueDeseralizationContext valueDeseralizationContext, CompoundTag tag) {
234
            super.readFromNBT(valueDeseralizationContext, tag);
×
235
            inventoryVariables.readFromNBT(tag, "variables");
×
236

237
            this.recipeSlotMessages.clear();
×
238
            CompoundTag recipeSlotErrorsTag = tag.getCompound("recipeSlotMessages");
×
239
            for (String slot : recipeSlotErrorsTag.getAllKeys()) {
×
240
                MutableComponent unlocalizedString = NBTClassType.readNbt(MutableComponent.class, slot, recipeSlotErrorsTag);
×
241
                this.recipeSlotMessages.put(Integer.parseInt(slot), unlocalizedString);
×
242
            }
×
243

244
            this.recipeSlotValidated.clear();
×
245
            CompoundTag recipeSlotValidatedTag = tag.getCompound("recipeSlotValidated");
×
246
            for (String slot : recipeSlotValidatedTag.getAllKeys()) {
×
247
                this.recipeSlotValidated.put(Integer.parseInt(slot), recipeSlotValidatedTag.getBoolean(slot));
×
248
            }
×
249

250
            this.disableCraftingCheck = tag.getBoolean("disableCraftingCheck");
×
251
        }
×
252

253
        @Override
254
        public void reloadRecipes(boolean initialize) {
255
            this.currentRecipes.clear();
×
256
            this.recipeSlotMessages.clear();
×
257
            this.recipeSlotValidated.clear();
×
258
            variableEvaluators.clear();
×
259
            for (int i = 0; i < getInventoryVariables().getContainerSize(); i++) {
×
260
                int slot = i;
×
261
                variableEvaluators.add(new InventoryVariableEvaluator<ValueObjectTypeRecipe.ValueRecipe>(
×
262
                        getInventoryVariables(), slot, valueDeseralizationContext, ValueTypes.OBJECT_RECIPE) {
×
263
                    @Override
264
                    public void onErrorsChanged() {
265
                        super.onErrorsChanged();
×
266
                        setLocalErrors(slot, getErrors());
×
267
                    }
×
268
                });
269
            }
270
            if (this.partNetwork != null) {
×
271
                for (int i = 0; i < getInventoryVariables().getContainerSize(); i++) {
×
272
                    reloadRecipe(i, initialize);
×
273
                }
274
            }
275
        }
×
276

277
        private void setLocalErrors(int slot, List<MutableComponent> errors) {
278
            if (errors.isEmpty()) {
×
279
                if (this.recipeSlotMessages.size() > slot) {
×
280
                    this.recipeSlotMessages.remove(slot);
×
281
                }
282
            } else {
283
                this.recipeSlotMessages.put(slot, errors.get(0));
×
284
            }
285
        }
×
286

287
        protected void reloadRecipe(int slot, boolean initialize) {
288
            this.currentRecipes.remove(slot);
×
289
            if (this.recipeSlotMessages.size() > slot) {
×
290
                this.recipeSlotMessages.remove(slot);
×
291
            }
292
            if (this.recipeSlotValidated.size() > slot) {
×
293
                this.recipeSlotValidated.remove(slot);
×
294
            }
295
            if (this.partNetwork != null) {
×
296
                InventoryVariableEvaluator<ValueObjectTypeRecipe.ValueRecipe> evaluator = variableEvaluators.get(slot);
×
297
                evaluator.refreshVariable(network, false);
×
298
                IVariable<ValueObjectTypeRecipe.ValueRecipe> variable = evaluator.getVariable(network);
×
299
                if (variable != null) {
×
300
                    try {
301
                        // Refresh the recipe if variable is changed
302
                        // The map is needed because we only want to register the listener once for each variable
303
                        if (!this.variableListeners.containsKey(variable)) {
×
304
                            variable.addInvalidationListener(() -> {
×
305
                                this.variableListeners.remove(variable);
×
306
                                delayedReloadRecipe(slot);
×
307
                            });
×
308
                            this.variableListeners.put(variable, true);
×
309
                        }
310

311
                        IValue value = variable.getValue();
×
312
                        if (value.getType() == ValueTypes.OBJECT_RECIPE) {
×
313
                            Optional<IRecipeDefinition> recipeWrapper = ((ValueObjectTypeRecipe.ValueRecipe) value).getRawValue();
×
314
                            if (recipeWrapper.isPresent()) {
×
315
                                IRecipeDefinition recipe = recipeWrapper.get();
×
316
                                if (!GeneralConfig.validateRecipesCraftingInterface || this.disableCraftingCheck || isValid(recipe)) {
×
317
                                    this.currentRecipes.put(slot, recipe);
×
318
                                    this.recipeSlotValidated.put(slot, true);
×
319
                                    this.recipeSlotMessages.put(slot, Component.translatable("gui.integratedcrafting.partinterface.slot.message.valid"));
×
320
                                } else {
321
                                    this.recipeSlotMessages.put(slot, Component.translatable("gui.integratedcrafting.partinterface.slot.message.invalid"));
×
322
                                }
323
                            }
324
                        } else {
×
325
                            this.recipeSlotMessages.put(slot, Component.translatable("gui.integratedcrafting.partinterface.slot.message.norecipe"));
×
326
                        }
327
                    } catch (EvaluationException e) {
×
328
                        this.recipeSlotMessages.put(slot, e.getErrorMessage());
×
329
                    }
×
330
                } else {
331
                    // If we're initializing, the variable might be referencing other variables that are not yet loaded.
332
                    // So let's retry once in the next tick.
333
                    if (initialize && evaluator.hasVariable()) {
×
334
                        this.delayedReloadRecipe(slot);
×
335
                    } else {
336
                        this.recipeSlotMessages.put(slot, Component.translatable("gui.integratedcrafting.partinterface.slot.message.norecipe"));
×
337
                    }
338
                }
339

340
                try {
341
                    IPartNetwork partNetwork = NetworkHelpers.getPartNetworkChecked(network);
×
342
                    MinecraftForge.EVENT_BUS.post(new PartVariableDrivenVariableContentsUpdatedEvent<>(network,
×
343
                            partNetwork, getTarget(),
×
344
                            PartTypes.INTERFACE_CRAFTING, this, lastPlayer, variable,
345
                            variable != null ? variable.getValue() : null));
×
346
                } catch (EvaluationException e) {
×
347
                    // Ignore error
348
                }
×
349
            }
350
            sendUpdate();
×
351
        }
×
352

353
        private void delayedReloadRecipe(int slot) {
354
            this.delayedRecipeReloads.add(slot);
×
355
        }
×
356

357

358
        private boolean isValid(IRecipeDefinition recipe) {
359
            DimPos dimPos = getTarget().getTarget().getPos();
×
360
            Direction side = getTarget().getTarget().getSide();
×
361
            IRecipeHandler recipeHandler = BlockEntityHelpers.getCapability(dimPos.getLevel(true), dimPos.getBlockPos(), side, Capabilities.RECIPE_HANDLER).orElse(null);
×
362
            if (recipeHandler == null) {
×
363
                BlockState blockState = dimPos.getLevel(true).getBlockState(dimPos.getBlockPos());
×
364
                recipeHandler = BlockCapabilities.getInstance().getCapability(blockState, Capabilities.RECIPE_HANDLER,
×
365
                        dimPos.getLevel(true), dimPos.getBlockPos(), side)
×
366
                .orElse(null);
×
367
            }
368
            if (recipeHandler != null) {
×
369
                IMixedIngredients simulatedOutput = recipeHandler.simulate(recipe);
×
370
                if (simulatedOutput != null && !simulatedOutput.isEmpty()) {
×
371
                    if (recipe.getOutput().containsAll(simulatedOutput)) {
×
372
                        return true;
×
373
                    } else {
374
                        if (GeneralConfig.logRecipeValidationFailures) {
×
375
                            IntegratedCrafting.clog(Level.INFO, "Recipe validation failure: incompatible recipe output and simulated output:\nRecipe output: " + recipe.getOutput() + "\nSimulated output: " + simulatedOutput);
×
376
                        }
377
                        return false;
×
378
                    }
379
                }
380
                if (GeneralConfig.logRecipeValidationFailures) {
×
381
                    IntegratedCrafting.clog(Level.INFO, "Recipe validation failure: No output was obtained when simulating a recipe\n" + recipe);
×
382
                }
383
                return false;
×
384
            }
385
            return true; // No recipe handler capability is present, so we can't confirm that the recipe will work.
×
386
        }
387

388
        @Override
389
        public void onDirty() {
390
            super.onDirty();
×
391

392
            // Unregister from the network, when all old recipes are still in place
393
            if (craftingNetwork != null) {
×
NEW
394
                craftingNetwork.removeCraftingInterface(getChannelCrafting(), this);
×
395
            }
396

397
            // Recalculate recipes
398
            if (getTarget() != null && !getTarget().getCenter().getPos().getLevel(true).isClientSide) {
×
399
                reloadRecipes(false);
×
400
            }
401

402
            // Re-register to the network, to force an update for all new recipes
403
            if (craftingNetwork != null) {
×
NEW
404
                craftingNetwork.addCraftingInterface(getChannelCrafting(), this);
×
405
            }
406
        }
×
407

408
        @Override
409
        public Collection<IRecipeDefinition> getRecipes() {
410
            return this.currentRecipes.values();
×
411
        }
412

413
        public Int2ObjectMap<IRecipeDefinition> getRecipesIndexed() {
414
            return currentRecipes;
×
415
        }
416

417
        public boolean isRecipeSlotValid(int slot) {
UNCOV
418
            return this.recipeSlotValidated.containsKey(slot);
×
419
        }
420

421
        @Nullable
422
        public MutableComponent getRecipeSlotUnlocalizedMessage(int slot) {
423
            return this.recipeSlotMessages.get(slot);
×
424
        }
425

426
        public IntSet getDelayedRecipeReloads() {
427
            return delayedRecipeReloads;
×
428
        }
429

430
        public void setDisableCraftingCheck(boolean disableCraftingCheck) {
431
            if (disableCraftingCheck != this.disableCraftingCheck) {
×
432
                this.disableCraftingCheck = disableCraftingCheck;
×
433

434
                this.sendUpdate();
×
435
            }
436
        }
×
437

438
        public boolean isDisableCraftingCheck() {
439
            return disableCraftingCheck;
×
440
        }
441
    }
442
}
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