Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
3edfe12
Add layout and camera for a crafting plan graph view
claude Oct 11, 2026
855510d
Add a pannable and zoomable graph view for crafting plans
claude Oct 11, 2026
3336244
Default to the crafting plan graph and remember the chosen view
claude Oct 11, 2026
bb6c7b5
Collapse large crafting plan graphs to an overview that fits
claude Oct 11, 2026
7103193
Move crafting plan buttons out of the view toggler
claude Oct 11, 2026
ba466c2
Calm down the crafting plan graph styling
claude Oct 11, 2026
e846aaf
Color the whole path to failed nodes in the crafting plan graph
claude Oct 11, 2026
c9bb478
Animate crafting nodes in the crafting plan graph
claude Oct 11, 2026
06b40bb
Experiment: running border animation for crafting nodes
claude Oct 11, 2026
cb35f97
Use a running border for crafting nodes and fix gaps in flowing edges
claude Oct 11, 2026
08071df
Keep completed jobs in the crafting plan graph and add keyboard controls
claude Oct 11, 2026
f48f9e9
Show collapsed crafting plan graph nodes as a stack with a count pill
claude Oct 11, 2026
553173b
Always show the crafting plan graph legend and stop folding completed…
claude Oct 11, 2026
096c54e
Address review findings in the crafting plan views
claude Oct 11, 2026
ae97d81
Show the root of the crafting plan graph as a larger node
claude Oct 11, 2026
ae428b6
Show crafting jobs whose inputs were refused as blocked instead of fa…
claude Oct 12, 2026
d13965f
Show the graph view button first
claude Oct 12, 2026
c46a51f
Hide the crafting plan tree list view by default
claude Oct 12, 2026
862ff10
Update Integrated Crafting to 1.7.2-840
claude Oct 12, 2026
3c7cf42
Show the progress of crafting jobs in the crafting plan views
claude Oct 12, 2026
30b6373
Only show the crafting plan durations in the progress bar for running…
claude Oct 12, 2026
a7f1b4c
Show the owner and channel of running jobs on the top line of the pla…
claude Oct 12, 2026
1ba4ac8
Stop multiplying the missing ingredients of running jobs by their crafts
claude Oct 13, 2026
e405e9b
Merge remote-tracking branch 'origin/master-1.21-lts' into fix/crafti…
claude Oct 16, 2026
2bcea39
Apply suggestion from @rubensworks
rubensworks Oct 16, 2026
7ed0617
Apply suggestion from @rubensworks
rubensworks Oct 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ protected static <I> void groupDependenciesByPrototype(IndexedEntries indexedEnt
for (List<IPrototypedIngredient<?, ?>> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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<IPrototypedIngredient<?, ?>> lastMissingIngredient : craftingPlan.getLastMissingIngredients()) {
List<List<IPrototypedIngredient>> 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<List<IPrototypedIngredient>> 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()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<PartTypeInterfaceCrafting.State> 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<Integer> 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<Integer> 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();
});
}

}
Loading