diff --git a/src/main/java/org/cyclops/integratedcrafting/api/crafting/CraftingJob.java b/src/main/java/org/cyclops/integratedcrafting/api/crafting/CraftingJob.java index f8dde97f..8145e069 100644 --- a/src/main/java/org/cyclops/integratedcrafting/api/crafting/CraftingJob.java +++ b/src/main/java/org/cyclops/integratedcrafting/api/crafting/CraftingJob.java @@ -385,6 +385,8 @@ public CraftingJob clone(CraftingHelpers.IIdentifierGenerator identifierGenerato getIngredientsStorage() ); clone.setAmountTotal(getAmountTotal()); + clone.setInitiatorUuid(getInitiatorUuid()); + clone.setNotifyInitiator(isNotifyInitiator()); return clone; } } diff --git a/src/main/java/org/cyclops/integratedcrafting/core/CraftingHelpers.java b/src/main/java/org/cyclops/integratedcrafting/core/CraftingHelpers.java index c5e4456b..14cbabab 100644 --- a/src/main/java/org/cyclops/integratedcrafting/core/CraftingHelpers.java +++ b/src/main/java/org/cyclops/integratedcrafting/core/CraftingHelpers.java @@ -727,6 +727,12 @@ public static void scheduleCraftingJobs(ICraftingNetwork craftingNetwork, List startedJobs = Lists.newArrayList(); craftingNetwork.getCraftingJobDependencyGraph().importDependencies(craftingJobDependencyGraph); for (CraftingJob craftingJob : craftingJobDependencyGraph.getCraftingJobs()) { + // Set before scheduling, as scheduling may distribute the job over multiple crafting + // interfaces, in which case it is replaced by clones that have to inherit this. + if (initiator != null) { + craftingJob.setInitiatorUuid(initiator.toString()); + craftingJob.setNotifyInitiator(notifyInitiator); + } try { craftingNetwork.scheduleCraftingJob(craftingJob, allowDistribution, storageGetter); } catch (UnavailableCraftingInterfacesException e) { @@ -740,10 +746,6 @@ public static void scheduleCraftingJobs(ICraftingNetwork craftingNetwork, throw new UnavailableCraftingInterfacesException(craftingJobDependencyGraph.getCraftingJobs()); } startedJobs.add(craftingJob); - if (initiator != null) { - craftingJob.setInitiatorUuid(initiator.toString()); - craftingJob.setNotifyInitiator(notifyInitiator); - } } } @@ -763,10 +765,12 @@ public static CraftingJob scheduleCraftingJob(ICraftingNetwork craftingNetwork, CraftingJob craftingJob, boolean allowDistribution, @Nullable UUID initiator) throws UnavailableCraftingInterfacesException { - craftingNetwork.scheduleCraftingJob(craftingJob, allowDistribution, storageGetter); + // Set before scheduling, as scheduling may distribute the job over multiple crafting + // interfaces, in which case it is replaced by clones that have to inherit this. if (initiator != null) { craftingJob.setInitiatorUuid(initiator.toString()); } + craftingNetwork.scheduleCraftingJob(craftingJob, allowDistribution, storageGetter); return craftingJob; } diff --git a/src/main/java/org/cyclops/integratedcrafting/gametest/GameTestsCraftingJobFinishedEvent.java b/src/main/java/org/cyclops/integratedcrafting/gametest/GameTestsCraftingJobFinishedEvent.java index 5afede28..ad2e9a7f 100644 --- a/src/main/java/org/cyclops/integratedcrafting/gametest/GameTestsCraftingJobFinishedEvent.java +++ b/src/main/java/org/cyclops/integratedcrafting/gametest/GameTestsCraftingJobFinishedEvent.java @@ -8,6 +8,7 @@ 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.neoforged.bus.api.SubscribeEvent; import net.neoforged.neoforge.common.NeoForge; @@ -26,6 +27,7 @@ import org.cyclops.integrateddynamics.api.network.IPositionedAddonsNetworkIngredients; import org.cyclops.integrateddynamics.core.helper.NetworkHelpers; +import java.util.Iterator; import java.util.List; import java.util.UUID; @@ -120,6 +122,58 @@ public void testNoEventOnCancelledJob(GameTestHelper helper) { .thenSucceed(); } + /** + * A job that is distributed over multiple crafting interfaces stays attributed to its initiator. + */ + @GameTest(template = TEMPLATE_EMPTY, timeoutTicks = TIMEOUT) + public void testDistributedJobRetainsInitiator(GameTestHelper helper) { + GameTestHelpersIntegratedCrafting.INetworkPositions positions = + GameTestHelpersIntegratedCrafting.createBasicNetwork(helper, POS, false, + Blocks.CRAFTING_TABLE, Blocks.CRAFTING_TABLE); + ChestBlockEntity chest = helper.getBlockEntity(POS.east()); + chest.setItem(0, new ItemStack(Items.OAK_PLANKS, 64)); + + // Both interfaces know the recipe, so the job gets split over the two of them + for (int i = 0; i < positions.interfaceRecipeAdders().size(); i++) { + positions.interfaceRecipeAdders().get(i).accept(Triple.of(0, RecipeType.CRAFTING, + ResourceLocation.fromNamespaceAndPath("minecraft", "chest"))); + } + + UUID initiator = UUID.randomUUID(); + EventCollector collector = EventCollector.start(initiator); + + helper.startSequence() + .thenIdle(20) + .thenExecute(() -> { + scheduleChestJob(helper, initiator, true, 4); + + List jobs = getCraftingJobs(helper); + helper.assertTrue(jobs.size() > 1, + "Expected the job to be split over both crafting interfaces, but found " + + jobs.size() + " job(s)"); + for (CraftingJob craftingJob : jobs) { + helper.assertTrue(initiator.toString().equals(craftingJob.getInitiatorUuid()), + "A split job lost its initiator"); + helper.assertTrue(craftingJob.isNotifyInitiator(), + "A split job lost its notify flag"); + } + }) + .thenWaitUntil(() -> helper.assertTrue(!collector.getRootJobs().isEmpty(), + "No completion event was emitted for the distributed job")) + .thenExecute(collector::stop) + .thenSucceed(); + } + + private static List getCraftingJobs(GameTestHelper helper) { + List jobs = Lists.newArrayList(); + Iterator it = CraftingHelpers.getCraftingNetworkChecked(getNetwork(helper)) + .getCraftingJobs(IPositionedAddonsNetworkIngredients.WILDCARD_CHANNEL); + while (it.hasNext()) { + jobs.add(it.next()); + } + return jobs; + } + private static void prepareNetwork(GameTestHelper helper) { GameTestHelpersIntegratedCrafting.INetworkPositions positions = GameTestHelpersIntegratedCrafting.createBasicNetwork(helper, POS); @@ -134,12 +188,17 @@ private static void prepareNetwork(GameTestHelper helper) { } private static CraftingJob scheduleChestJob(GameTestHelper helper, UUID initiator, boolean notifyInitiator) { + return scheduleChestJob(helper, initiator, notifyInitiator, 1); + } + + private static CraftingJob scheduleChestJob(GameTestHelper helper, UUID initiator, boolean notifyInitiator, + int amount) { INetwork network = getNetwork(helper); int channel = IPositionedAddonsNetworkIngredients.DEFAULT_CHANNEL; try { CraftingJobDependencyGraph dependencyGraph = new CraftingJobDependencyGraph(); CraftingJob craftingJob = CraftingHelpers.calculateCraftingJobs(network, channel, - IngredientComponents.ITEMSTACK, new ItemStack(Items.CHEST), ItemMatch.ITEM, true, + IngredientComponents.ITEMSTACK, new ItemStack(Items.CHEST, amount), ItemMatch.ITEM, true, CraftingHelpers.getGlobalCraftingJobIdentifier(), dependencyGraph, false); CraftingHelpers.scheduleCraftingJobs(CraftingHelpers.getCraftingNetworkChecked(network), CraftingHelpers.getNetworkStorageGetter(network, channel, false), dependencyGraph, true, diff --git a/src/test/java/org/cyclops/integratedcrafting/api/crafting/TestCraftingJob.java b/src/test/java/org/cyclops/integratedcrafting/api/crafting/TestCraftingJob.java index a85eb34b..ba30ec6b 100644 --- a/src/test/java/org/cyclops/integratedcrafting/api/crafting/TestCraftingJob.java +++ b/src/test/java/org/cyclops/integratedcrafting/api/crafting/TestCraftingJob.java @@ -35,4 +35,25 @@ public void testAmountTotalIsUnaffectedByCrafting() { assertThat(job.getAmountTotal(), equalTo(3)); } + @Test + public void testCloneRetainsInitiator() { + // Jobs are cloned when they are distributed over multiple crafting interfaces, + // so the clones have to remain attributable to whoever requested them. + job.setInitiatorUuid("00000000-0000-0000-0000-00000000beef"); + job.setNotifyInitiator(true); + + CraftingJob clone = job.clone(() -> 1); + + assertThat(clone.getInitiatorUuid(), equalTo("00000000-0000-0000-0000-00000000beef")); + assertThat(clone.isNotifyInitiator(), equalTo(true)); + } + + @Test + public void testCloneWithoutInitiator() { + CraftingJob clone = job.clone(() -> 1); + + assertThat(clone.getInitiatorUuid(), equalTo(null)); + assertThat(clone.isNotifyInitiator(), equalTo(false)); + } + }