From 0da701ead42915cc7181210baa12b3eaa30bdfe4 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 12 Oct 2026 16:32:35 +0000 Subject: [PATCH 1/2] Stop reporting invalid inputs for jobs that only wait for their interface When the target of a crafting interface refused a job's inputs, for example a furnace that was still busy, the job was marked as having invalid inputs. Jobs are only tested again when the interface has room to start one, so once another job started, the refused jobs kept that mark until the running job was done, even if the target had accepted inputs again in the meantime. Terminals showed them as failed for that whole time. Starting a job now clears the mark on the other pending jobs of the same interface, as their earlier test no longer applies. They are tested again on the next attempt, so a target that keeps refusing is still reported. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01C5iUzweRYoCkKB5bvJdH8Y --- .../core/CraftingJobHandler.java | 8 ++ .../GameTestsItemsCraftBusyTarget.java | 100 ++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 src/main/java/org/cyclops/integratedcrafting/gametest/GameTestsItemsCraftBusyTarget.java diff --git a/src/main/java/org/cyclops/integratedcrafting/core/CraftingJobHandler.java b/src/main/java/org/cyclops/integratedcrafting/core/CraftingJobHandler.java index f6b1aaec8..25551916d 100644 --- a/src/main/java/org/cyclops/integratedcrafting/core/CraftingJobHandler.java +++ b/src/main/java/org/cyclops/integratedcrafting/core/CraftingJobHandler.java @@ -658,6 +658,14 @@ public void update(INetwork network, int channel, PartPos targetPos) { // Start the crafting job if (startingCraftingJob != null) { + // 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. + for (CraftingJob pendingCraftingJob : getPendingCraftingJobs()) { + if (pendingCraftingJob != startingCraftingJob) { + pendingCraftingJob.setInvalidInputs(false); + } + } + // Check if the job was started while blocking mode was enabled in this handler boolean blockingMode = !nonBlockingJobsRunningAmount.containsKey(startingCraftingJob.getId()) || startingCraftingJob.getAmount() == 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()); + } +} From 02bc1960caabee7333885dad50d0c5f40aa23135 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 12 Oct 2026 16:59:59 +0000 Subject: [PATCH 2/2] Only clear refused jobs once the started job was inserted If the target refused the inputs on insertion after all, its state did not change, so the other jobs keep their mark. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01C5iUzweRYoCkKB5bvJdH8Y --- .../core/CraftingJobHandler.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/cyclops/integratedcrafting/core/CraftingJobHandler.java b/src/main/java/org/cyclops/integratedcrafting/core/CraftingJobHandler.java index 25551916d..3b40e4ac8 100644 --- a/src/main/java/org/cyclops/integratedcrafting/core/CraftingJobHandler.java +++ b/src/main/java/org/cyclops/integratedcrafting/core/CraftingJobHandler.java @@ -658,20 +658,20 @@ public void update(INetwork network, int channel, PartPos targetPos) { // Start the crafting job if (startingCraftingJob != null) { - // 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. - for (CraftingJob pendingCraftingJob : getPendingCraftingJobs()) { - if (pendingCraftingJob != startingCraftingJob) { - pendingCraftingJob.setInvalidInputs(false); - } - } - // Check if the job was started while blocking mode was enabled in this handler boolean blockingMode = !nonBlockingJobsRunningAmount.containsKey(startingCraftingJob.getId()) || startingCraftingJob.getAmount() == 1; // 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);