diff --git a/src/main/java/org/cyclops/integratedcrafting/core/CraftingJobHandler.java b/src/main/java/org/cyclops/integratedcrafting/core/CraftingJobHandler.java index f6b1aaec8..3b40e4ac8 100644 --- a/src/main/java/org/cyclops/integratedcrafting/core/CraftingJobHandler.java +++ b/src/main/java/org/cyclops/integratedcrafting/core/CraftingJobHandler.java @@ -664,6 +664,14 @@ public void update(INetwork network, int channel, PartPos targetPos) { // Start the actual crafting boolean couldCraft = consumeAndInsertCrafting(blockingMode, network, channel, targetPos, startingCraftingJob); + // Other jobs that the target refused were tested against its previous state, + // so they are only waiting for this handler now, until they are tested again. + if (couldCraft) { + for (CraftingJob pendingCraftingJob : getPendingCraftingJobs()) { + pendingCraftingJob.setInvalidInputs(false); + } + } + // Keep inserting as much as possible if non-blocking if (couldCraft && !blockingMode) { nonBlockingJobsRunningAmount.put(startingCraftingJob.getId(), 1); diff --git a/src/main/java/org/cyclops/integratedcrafting/gametest/GameTestsItemsCraftBusyTarget.java b/src/main/java/org/cyclops/integratedcrafting/gametest/GameTestsItemsCraftBusyTarget.java new file mode 100644 index 000000000..a94459b4a --- /dev/null +++ b/src/main/java/org/cyclops/integratedcrafting/gametest/GameTestsItemsCraftBusyTarget.java @@ -0,0 +1,100 @@ +package org.cyclops.integratedcrafting.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.Blocks; +import net.minecraft.world.level.block.entity.ChestBlockEntity; +import net.minecraft.world.level.block.entity.FurnaceBlockEntity; +import net.neoforged.neoforge.gametest.GameTestHolder; +import net.neoforged.neoforge.gametest.PrefixGameTestTemplate; +import org.apache.commons.lang3.tuple.Triple; +import org.cyclops.commoncapabilities.IngredientComponents; +import org.cyclops.commoncapabilities.api.capability.itemhandler.ItemMatch; +import org.cyclops.integratedcrafting.Reference; +import org.cyclops.integratedcrafting.api.crafting.CraftingJob; +import org.cyclops.integratedcrafting.api.crafting.CraftingJobStatus; +import org.cyclops.integratedcrafting.api.network.ICraftingNetwork; +import org.cyclops.integratedcrafting.core.CraftingHelpers; +import org.cyclops.integratedcrafting.part.PartTypeInterfaceCrafting; +import org.cyclops.integrateddynamics.api.network.INetwork; +import org.cyclops.integrateddynamics.api.network.IPositionedAddonsNetwork; +import org.cyclops.integrateddynamics.core.helper.NetworkHelpers; + +import static org.cyclops.integratedcrafting.gametest.GameTestHelpersIntegratedCrafting.createBasicNetwork; + +/** + * Tests for crafting interfaces whose target temporarily refuses the recipe inputs. + * + * @author rubensworks + */ +@GameTestHolder(Reference.MOD_ID) +@PrefixGameTestTemplate(false) +public class GameTestsItemsCraftBusyTarget { + + public static final String TEMPLATE_EMPTY = "empty10"; + public static final int TIMEOUT = 600; + public static final BlockPos POS = BlockPos.ZERO.offset(2, 0, 2); + + /** + * Jobs that a busy target refused must not keep reporting invalid inputs + * while they are only waiting for another job of the same interface. + */ + @GameTest(template = TEMPLATE_EMPTY, timeoutTicks = TIMEOUT) + public void testRefusedJobsWaitForInterfaceOnceAnotherJobStarts(GameTestHelper helper) { + GameTestHelpersIntegratedCrafting.INetworkPositions positions = + createBasicNetwork(helper, POS, Blocks.FURNACE); + ChestBlockEntity chest = helper.getBlockEntity(POS.east()); + chest.setItem(0, new ItemStack(Items.RAW_IRON, 2)); + positions.interfaceRecipeAdders().get(0).accept(Triple.of(0, RecipeType.SMELTING, + ResourceLocation.fromNamespaceAndPath("minecraft", "iron_ingot_from_smelting_raw_iron"))); + + // The furnace is busy with something else, so it refuses raw iron for a while + FurnaceBlockEntity furnace = helper.getBlockEntity(POS.west()); + furnace.setItem(0, new ItemStack(Items.RAW_GOLD, 1)); + + PartTypeInterfaceCrafting.State interfaceState = positions.interfaceStates().get(0); + CraftingJob[] jobs = new CraftingJob[2]; + boolean[] refused = new boolean[1]; + + helper.runAfterDelay(5, () -> { + INetwork network = getNetwork(positions); + for (int i = 0; i < jobs.length; i++) { + jobs[i] = CraftingHelpers.calculateAndScheduleCraftingJob(network, IPositionedAddonsNetwork.DEFAULT_CHANNEL, + IngredientComponents.ITEMSTACK, new ItemStack(Items.IRON_INGOT), ItemMatch.ITEM, false, false, + CraftingHelpers.getGlobalCraftingJobIdentifier(), null); + helper.assertTrue(jobs[i] != null, "Crafting job " + i + " could not be scheduled"); + } + }); + + helper.onEachTick(() -> { + if (jobs[1] == null) { + return; + } + ICraftingNetwork craftingNetwork = CraftingHelpers.getCraftingNetworkChecked(getNetwork(positions)); + CraftingJobStatus status0 = interfaceState.getCraftingJobStatus(craftingNetwork, jobs[0].getChannel(), jobs[0].getId()); + CraftingJobStatus status1 = interfaceState.getCraftingJobStatus(craftingNetwork, jobs[1].getChannel(), jobs[1].getId()); + if (status0 == CraftingJobStatus.INVALID_INPUTS && status1 == CraftingJobStatus.INVALID_INPUTS) { + refused[0] = true; + } + + // Once one job runs, the other one only waits for the interface + CraftingJobStatus waiting = status0 == CraftingJobStatus.PROCESSING ? status1 + : status1 == CraftingJobStatus.PROCESSING ? status0 : null; + if (waiting != null) { + helper.assertTrue(refused[0], "The busy furnace should have refused both jobs first"); + helper.assertValueEqual(waiting, CraftingJobStatus.PENDING_INTERFACE, "Status of the waiting job"); + helper.succeed(); + } + }); + } + + protected static INetwork getNetwork(GameTestHelpersIntegratedCrafting.INetworkPositions positions) { + return NetworkHelpers.getNetworkChecked(positions.writer().getPos().getLevel(true), + positions.writer().getPos().getBlockPos(), positions.writer().getSide()); + } +}