From edfd602440b2f92f4c059fdec6f84863a45d7da3 Mon Sep 17 00:00:00 2001 From: Aintripin <72869676+Aintripin@users.noreply.github.com> Date: Sun, 18 Oct 2026 01:43:04 -0700 Subject: [PATCH] Fix crafting using only one of several patterns with the same priority (#1695) --- .../java/appeng/test/CraftingV2Tests.java | 96 +++++++++++++++++++ .../appeng/me/cache/CraftingGridCache.java | 12 ++- 2 files changed, 104 insertions(+), 4 deletions(-) diff --git a/src/functionalTest/java/appeng/test/CraftingV2Tests.java b/src/functionalTest/java/appeng/test/CraftingV2Tests.java index 0b853068d..4bcb5ea98 100644 --- a/src/functionalTest/java/appeng/test/CraftingV2Tests.java +++ b/src/functionalTest/java/appeng/test/CraftingV2Tests.java @@ -22,6 +22,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import appeng.api.networking.crafting.ICraftingPatternDetails; import appeng.api.storage.data.IAEItemStack; import appeng.crafting.v2.CraftingJobV2; import appeng.crafting.v2.CraftingRequest; @@ -712,4 +713,99 @@ public class CraftingV2Tests { assertEquals(1, goldDustRequests.get(0).stack.getStackSize()); assertEquals(0, goldDustRequests.get(0).remainingToProcess); } + + private void addEqualPriorityPlateRecipes(MockAESystem aeSystem) { + aeSystem.newProcessingPattern().addInput(withSize(ironDust, 2)) // + .addOutput(withSize(ironIngot, 1)) // + .buildAndAdd(); + aeSystem.newProcessingPattern().addInput(withSize(ironIngot, 1)) // + .addOutput(withSize(ironPlate, 1)) // + .buildAndAdd(); + aeSystem.newProcessingPattern().addInput(withSize(goldDust, 1)) // + .addOutput(withSize(ironPlate, 1)) // + .buildAndAdd(); + } + + @Test + void competingRecipeSamePriority() { + MockAESystem aeSystem = new MockAESystem(dummyWorld); + aeSystem.addStoredItem(withSize(ironDust, 4)); + aeSystem.addStoredItem(withSize(goldDust, 1)); + addEqualPriorityPlateRecipes(aeSystem); + + final CraftingJobV2 job = aeSystem.makeCraftingJob(withSize(ironPlate, 3)); + simulateJobAndCheck(job, MAX_SIMULATION_STEPS); + assertFalse(job.isSimulation()); + assertEquals(job.getOutput(), AEItemStack.create(withSize(ironPlate, 3))); + assertJobPlanEquals( + job, + AEItemStack.create(withSize(ironDust, 4)), + AEItemStack.create(withSize(goldDust, 1)), + AEItemStack.create(withSize(ironIngot, 0)).setCountRequestable(2), + AEItemStack.create(withSize(ironPlate, 0)).setCountRequestable(3)); + + final CraftingJobV2 jobFailed = aeSystem.makeCraftingJob(withSize(ironPlate, 4)); + simulateJobAndCheck(jobFailed, MAX_SIMULATION_STEPS); + assertTrue(jobFailed.isSimulation()); + assertJobPlanEquals( + jobFailed, + AEItemStack.create(withSize(ironDust, 6)), + AEItemStack.create(withSize(goldDust, 1)), + AEItemStack.create(withSize(ironIngot, 0)).setCountRequestable(3), + AEItemStack.create(withSize(ironPlate, 0)).setCountRequestable(4)); + } + + @Test + void competingRecipeSamePriorityIronDustOnly() { + MockAESystem aeSystem = new MockAESystem(dummyWorld); + aeSystem.addStoredItem(withSize(ironDust, 2)); + addEqualPriorityPlateRecipes(aeSystem); + + final CraftingJobV2 job = aeSystem.makeCraftingJob(withSize(ironPlate, 1)); + simulateJobAndCheck(job, MAX_SIMULATION_STEPS); + assertFalse(job.isSimulation()); + assertJobPlanEquals( + job, + AEItemStack.create(withSize(ironDust, 2)), + AEItemStack.create(withSize(ironIngot, 0)).setCountRequestable(1), + AEItemStack.create(withSize(ironPlate, 0)).setCountRequestable(1)); + } + + @Test + void competingRecipeSamePriorityGoldDustOnly() { + MockAESystem aeSystem = new MockAESystem(dummyWorld); + aeSystem.addStoredItem(withSize(goldDust, 1)); + addEqualPriorityPlateRecipes(aeSystem); + + final CraftingJobV2 job = aeSystem.makeCraftingJob(withSize(ironPlate, 1)); + simulateJobAndCheck(job, MAX_SIMULATION_STEPS); + assertFalse(job.isSimulation()); + assertJobPlanEquals( + job, + AEItemStack.create(withSize(goldDust, 1)), + AEItemStack.create(withSize(ironPlate, 0)).setCountRequestable(1)); + } + + @Test + void competingRecipePatternsListedByPriority() { + MockAESystem aeSystem = new MockAESystem(dummyWorld); + addEqualPriorityPlateRecipes(aeSystem); + aeSystem.newProcessingPattern().addInput(withSize(goldIngot, 1)) // + .addOutput(withSize(ironPlate, 1)) // + .setPriority(1) // + .buildAndAdd(); + aeSystem.newProcessingPattern().addInput(withSize(ironDust, 1)) // + .addOutput(withSize(ironPlate, 1)) // + .setPriority(-1) // + .buildAndAdd(); + aeSystem.cgCache.setMockPatternsFromMethods(); + + final List platePatterns = new ArrayList<>( + aeSystem.cgCache.getCraftingFor(AEItemStack.create(withSize(ironPlate, 1)), null, 0, dummyWorld)); + assertEquals(4, platePatterns.size()); + assertEquals(1, platePatterns.get(0).getPriority()); + assertEquals(0, platePatterns.get(1).getPriority()); + assertEquals(0, platePatterns.get(2).getPriority()); + assertEquals(-1, platePatterns.get(3).getPriority()); + } } diff --git a/src/main/java/appeng/me/cache/CraftingGridCache.java b/src/main/java/appeng/me/cache/CraftingGridCache.java index 3db01bca0..6c7fcf646 100644 --- a/src/main/java/appeng/me/cache/CraftingGridCache.java +++ b/src/main/java/appeng/me/cache/CraftingGridCache.java @@ -20,12 +20,12 @@ import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; import java.util.Set; -import java.util.TreeSet; import java.util.UUID; import java.util.WeakHashMap; import java.util.concurrent.ExecutorService; @@ -357,7 +357,7 @@ public class CraftingGridCache craftableItemSubstitutes.put(ais, details); } - Set methods = tmpCraft.computeIfAbsent(out, k -> new TreeSet<>(COMPARATOR)); + Set methods = tmpCraft.computeIfAbsent(out, k -> new LinkedHashSet<>()); methods.add(details); } @@ -367,10 +367,14 @@ public class CraftingGridCache // make them immutable for (final Entry, Set> e : tmpCraft.entrySet()) { - this.craftableItems.put(e.getKey(), ImmutableList.copyOf(e.getValue())); + // sort a list: a set ordered by priority alone would drop patterns of equal priority + final List patterns = new ArrayList<>(e.getValue()); + patterns.sort(Comparator.comparing(ICraftingPatternDetails::getPriority).reversed()); + final ImmutableList immutable = ImmutableList.copyOf(patterns); + this.craftableItems.put(e.getKey(), immutable); final IAEItemStack ais = stackConvert(e.getKey()); - if (ais != null) craftableItemsLegacy.put(ais, ImmutableList.copyOf(e.getValue())); + if (ais != null) craftableItemsLegacy.put(ais, immutable); } this.craftableItemsSnapshot = ImmutableMap.copyOf(this.craftableItems);