diff --git a/src/main/java/org/cyclops/integratedterminals/api/terminalstorage/crafting/TerminalCraftingPlanStatic.java b/src/main/java/org/cyclops/integratedterminals/api/terminalstorage/crafting/TerminalCraftingPlanStatic.java index c771922ae..2b6e18fd4 100644 --- a/src/main/java/org/cyclops/integratedterminals/api/terminalstorage/crafting/TerminalCraftingPlanStatic.java +++ b/src/main/java/org/cyclops/integratedterminals/api/terminalstorage/crafting/TerminalCraftingPlanStatic.java @@ -349,7 +349,7 @@ protected static void groupDependenciesByPrototype(IndexedEntries indexedEnt for (List> outputVariants : plan.getLastMissingIngredients()) { TerminalCraftingPlanFlatStatic.Entry entry = indexedEntries.get(outputVariants); long quantity = IndexedEntries.getQuantity(outputVariants); - entry.setQuantityMissing(entry.getQuantityMissing() + quantity * plan.getCraftingQuantity()); + entry.setQuantityMissing(entry.getQuantityMissing() + quantity); } // Recurse into dependencies diff --git a/src/main/java/org/cyclops/integratedterminals/client/gui/container/component/GuiCraftingPlan.java b/src/main/java/org/cyclops/integratedterminals/client/gui/container/component/GuiCraftingPlan.java index 1a0b04558..1a699f5b7 100644 --- a/src/main/java/org/cyclops/integratedterminals/client/gui/container/component/GuiCraftingPlan.java +++ b/src/main/java/org/cyclops/integratedterminals/client/gui/container/component/GuiCraftingPlan.java @@ -18,10 +18,8 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.block.Blocks; import org.apache.commons.lang3.time.DurationFormatUtils; -import org.cyclops.commoncapabilities.api.ingredient.IIngredientMatcher; import org.cyclops.commoncapabilities.api.ingredient.IPrototypedIngredient; import org.cyclops.commoncapabilities.api.ingredient.IngredientComponent; -import org.cyclops.commoncapabilities.api.ingredient.PrototypedIngredient; import org.cyclops.cyclopscore.client.gui.GuiGraphicsExtended; import org.cyclops.cyclopscore.client.gui.component.WidgetScrollBar; import org.cyclops.cyclopscore.client.gui.image.Image; @@ -34,6 +32,7 @@ import org.cyclops.integratedterminals.api.terminalstorage.crafting.ITerminalCraftingPlan; import org.cyclops.integratedterminals.api.terminalstorage.crafting.ITerminalCraftingPlanFlat; import org.cyclops.integratedterminals.api.terminalstorage.crafting.TerminalCraftingJobStatus; +import org.cyclops.integratedterminals.api.terminalstorage.crafting.TerminalCraftingPlanStatic; import org.cyclops.integratedterminals.client.gui.container.ContainerScreenTerminalStorage; import org.cyclops.integratedterminals.core.client.gui.CraftingOptionGuiData; import org.lwjgl.opengl.GL11; @@ -407,17 +406,10 @@ protected static void addElements(@Nullable Element parent, int indent, ITermina if (craftingPlan.getStatus() == TerminalCraftingJobStatus.PENDING_INPUTS) { // Add last missing ingredients for (List> lastMissingIngredient : craftingPlan.getLastMissingIngredients()) { - List> outputs = Collections.singletonList(lastMissingIngredient - .stream() - .map(prototypedIngredient -> { - IIngredientMatcher matcher = prototypedIngredient.getComponent().getMatcher(); - Object instance = matcher.withQuantity(prototypedIngredient.getPrototype(), - matcher.getQuantity(prototypedIngredient.getPrototype()) * craftingPlan.getCraftingQuantity()); - return new PrototypedIngredient(prototypedIngredient.getComponent(), instance, prototypedIngredient.getCondition()); - }) - .collect(Collectors.toList())); + List> outputs = Collections.singletonList(Lists.newArrayList(lastMissingIngredient)); elements.add(currentElement.addChild(new Element(elementId++, indent + 1, outputs, - 0, 0, craftingPlan.getCraftingQuantity(), TerminalCraftingJobStatus.INVALID.getColor(), TerminalCraftingJobStatus.INVALID))); + 0, 0, TerminalCraftingPlanStatic.IndexedEntries.getQuantity(lastMissingIngredient), + TerminalCraftingJobStatus.INVALID.getColor(), TerminalCraftingJobStatus.INVALID))); } } else if (craftingPlan.getStatus() != TerminalCraftingJobStatus.CRAFTING) { for (IPrototypedIngredient bufferedIngredient : craftingPlan.getBufferedIngredients()) { diff --git a/src/main/java/org/cyclops/integratedterminals/gametest/GameTestTerminalCraftingPlanMissing.java b/src/main/java/org/cyclops/integratedterminals/gametest/GameTestTerminalCraftingPlanMissing.java new file mode 100644 index 000000000..794ce2908 --- /dev/null +++ b/src/main/java/org/cyclops/integratedterminals/gametest/GameTestTerminalCraftingPlanMissing.java @@ -0,0 +1,107 @@ +package org.cyclops.integratedterminals.gametest; + +import net.minecraft.core.BlockPos; +import net.minecraft.gametest.framework.GameTest; +import net.minecraft.gametest.framework.GameTestHelper; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.crafting.RecipeType; +import net.minecraft.world.level.block.entity.ChestBlockEntity; +import net.neoforged.neoforge.gametest.GameTestHolder; +import net.neoforged.neoforge.gametest.PrefixGameTestTemplate; +import org.apache.commons.lang3.tuple.Triple; +import org.cyclops.commoncapabilities.api.capability.recipehandler.IRecipeDefinition; +import org.cyclops.commoncapabilities.api.ingredient.IngredientComponent; +import org.cyclops.integratedcrafting.api.crafting.UnavailableCraftingInterfacesException; +import org.cyclops.integratedcrafting.core.CraftingHelpers; +import org.cyclops.integratedcrafting.gametest.GameTestHelpersIntegratedCrafting; +import org.cyclops.integratedcrafting.part.PartTypeInterfaceCrafting; +import org.cyclops.integrateddynamics.api.network.INetwork; +import org.cyclops.integrateddynamics.api.network.IPositionedAddonsNetworkIngredients; +import org.cyclops.integrateddynamics.core.helper.NetworkHelpers; +import org.cyclops.integratedterminals.Reference; +import org.cyclops.integratedterminals.api.terminalstorage.crafting.ITerminalCraftingPlan; +import org.cyclops.integratedterminals.api.terminalstorage.crafting.ITerminalCraftingPlanFlat; +import org.cyclops.integratedterminals.api.terminalstorage.crafting.TerminalCraftingJobStatus; +import org.cyclops.integratedterminals.modcompat.integratedcrafting.TerminalCraftingOptionRecipeDefinition; +import org.cyclops.integratedterminals.modcompat.integratedcrafting.TerminalCraftingPlanCraftingJobDependencyGraph; +import org.cyclops.integratedterminals.modcompat.integratedcrafting.TerminalStorageTabIngredientCraftingHandlerCraftingNetwork; + +/** + * Game tests for running crafting jobs that are missing ingredients. + * @author rubensworks + */ +@GameTestHolder(Reference.MOD_ID) +@PrefixGameTestTemplate(false) +public class GameTestTerminalCraftingPlanMissing { + + public static final BlockPos POS = BlockPos.ZERO.offset(2, 0, 2); + public static final int TIMEOUT = 2000; + + /** + * The missing ingredients of a running job are its total missing quantities, + * so they must not be multiplied by its remaining crafts. + */ + @GameTest(template = "empty10", templateNamespace = Reference.MOD_ID, timeoutTicks = TIMEOUT) + public void testCraftingPlanMissingQuantities(GameTestHelper helper) { + GameTestHelpersIntegratedCrafting.INetworkPositions positions = + GameTestHelpersIntegratedCrafting.createBasicNetwork(helper, POS); + ChestBlockEntity chest = helper.getBlockEntity(POS.east()); + chest.setItem(0, new ItemStack(Items.OAK_PLANKS, 16)); + positions.interfaceRecipeAdders().get(0).accept(Triple.of(0, RecipeType.CRAFTING, ResourceLocation.fromNamespaceAndPath("minecraft", "chest"))); + TerminalStorageTabIngredientCraftingHandlerCraftingNetwork handler = new TerminalStorageTabIngredientCraftingHandlerCraftingNetwork(); + Integer[] jobId = new Integer[1]; + + helper.onEachTick(() -> { + INetwork network = NetworkHelpers.getNetwork(helper.getLevel(), helper.absolutePos(POS), null).orElse(null); + if (network == null) { + return; + } + if (jobId[0] == null) { + IRecipeDefinition recipe = CraftingHelpers.getCraftingNetworkChecked(network) + .getRecipeCraftingInterfaces(IPositionedAddonsNetworkIngredients.DEFAULT_CHANNEL).keySet().stream() + .filter(r -> r.getOutput().getInstances(IngredientComponent.ITEMSTACK).stream().anyMatch(s -> s.getItem() == Items.CHEST)) + .findFirst() + .orElse(null); + if (recipe == null) { + return; + } + + // Plan 2 chests, but take 10 of their 16 planks away before the job is started + ITerminalCraftingPlan plan = handler.calculateCraftingPlan(network, IPositionedAddonsNetworkIngredients.DEFAULT_CHANNEL, + new TerminalCraftingOptionRecipeDefinition<>(IngredientComponent.ITEMSTACK, recipe), 2); + if (plan.getStatus() != TerminalCraftingJobStatus.UNSTARTED) { + // The planks are not indexed in storage yet + return; + } + chest.setItem(0, new ItemStack(Items.OAK_PLANKS, 6)); + try { + CraftingHelpers.scheduleCraftingJobs(CraftingHelpers.getCraftingNetworkChecked(network), + CraftingHelpers.getNetworkStorageGetter(network, IPositionedAddonsNetworkIngredients.DEFAULT_CHANNEL, false), + ((TerminalCraftingPlanCraftingJobDependencyGraph) plan).getCraftingJobDependencyGraph(), true, null); + } catch (UnavailableCraftingInterfacesException e) { + helper.fail("The crafting job could not be started"); + } + jobId[0] = plan.getId(); + return; + } + + ITerminalCraftingPlan plan = handler.getCraftingJob(network, IPositionedAddonsNetworkIngredients.DEFAULT_CHANNEL, jobId[0]); + helper.assertTrue(plan != null, "The crafting job is not running"); + if (plan.getStatus() != TerminalCraftingJobStatus.PENDING_INPUTS) { + return; + } + helper.assertValueEqual(plan.getCraftingQuantity(), 2L, "Remaining crafts of the job"); + + ITerminalCraftingPlanFlat.IEntry planks = plan.flatten().getEntries().stream() + .filter(e -> ((ItemStack) e.getInstances().get(0).getPrototype()).getItem() == Items.OAK_PLANKS) + .findFirst() + .orElse(null); + helper.assertTrue(planks != null, "The planks are not in the flattened plan"); + helper.assertValueEqual(planks.getQuantityMissing(), 10L, "Missing planks"); + helper.succeed(); + }); + } + +}