Skip to content
31 changes: 31 additions & 0 deletions docs/content/Gameplay/Logistics/Machines.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,37 @@ the power button in its UI or by right-clicking it with a Soft Mallet.

Buses and Hatches can accept automated import or export from other sides, so long as something else is causing it.

### Distinct, Painted, and Filtered Inputs and Outputs
Under normal circumstances, **all** input Buses and Hatches and on a multiblock machine will be checked for recipe
inputs, and all output buses and hatches will be used to place recipe outputs. However, this can lead to unwanted behavior
where a user wants a single machine to do multiple recipes, but the ingredients to those recipes can conflict and be used
to run an unwanted third recipe. Additionally, when a machine is being used like this, the output bus/hatch to which
produced items/fluids are delivered is chosen somewhat arbitrarily, making it difficult or unwieldy to plan pipes to carry
specific output products away.

GTM offers three tools for this problem: Fluid Hatch Filter Locking, Distinct Buses and Painted Buses/Hatches.

* Fluid Hatch Filter Locking is a simple system for resolving the problem of deciding what output hatches receive what
produced fluids. Using the same interface as a Super Tank, a Fluid Output Hatch can have its current contained fluid
Locked, meaning that only that fluid will ever be placed in it; or the Hatch can be pre-emptively locked to a fluid by
dragging that fluid from JEI/EMI into the Hatch's output slot.
* Filter Locking only works with standard *single* fluid hatches, and cannot be done to the higher-tier Quadruple or
Nonuple Fluid Hatches. (However, those Hatches also cannot contain a single fluid in more than one slot, so they do
still allow for some degree of output separation when used with Quadruple or Nonuple Fluid Pipes.)
* Distinct Buses is a toggle used on Input Buses (not Hatches), which causes the machine to look at this bus as being
separate from all other Distinct Buses. (One distinct bus has no meaning, but two distinct buses on one machine will
cause the machine to search each distinct bus separately).
* Painted Buses/Hatches are hatches which have been Painted using a can of spray paint. Input Buses/Hatches which have been
Painted in the same color are looked at together by the machine searching for recipe inputs, but any items/fluids stored
in buses/hatches with a different color are not used for the search. The isolation works the same as for Distinct Buses,
but it allows for multiple buses/hatches to be searched together as a group.
* Prior to version 7.5.0, painting **Output** Buses/Hatches had no effect. Version 7.5.0 introduced machines pairing their
Painted Outputs to their Painted Inputs, such that if a recipe pulls items from a Painted Input, it can only output the
products of that recipe to a Painted Output of the same color (or an unpainted output).
* Buses and Hatches that are not painted, or not set to Distinct, are always fair game for the machine - Distinct and
Painted Inputs can always pull from other non-distinct and non-painted inputs; and recipes that used ingredients from
Painted Inputs can always send their products to non-painted outputs.

## Passthrough Hatches and the Cleanroom
The Cleanroom is a unique multiblock with unique restrictions. Because the Cleanroom must have solid walls, pipes, cables,
and inventories outside cannot directly connect to machines inside. For this purpose, Passthrough Hatches exist.
Expand Down
22 changes: 20 additions & 2 deletions docs/content/Modpacks/Changes/v7.5.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
title: "Version 7.5.0"
---


# Updating from `7.4.1` to `7.5.0`
## Machine Builder Generics
We have added a second Generic argument to our (Multiblock)MachineBuilder. This effectively means that anywhere where you used to store a partially finished `MachineBuilder<?>`, you now need to store a `MachineBuilder<?, ?>`. The same holds for `MultiblockMachineBuilder<?, ?>`.
Expand Down Expand Up @@ -74,4 +73,23 @@ you should **NOT** use `PipeModel#dynamicModel()` and instead set the model with
## Lamp Predicates
Previously, lamps were not useable with the terminal in multiblocks. There are new lamp predicates that will help solve this problem.
The predicate to use all lamps is: `Predicates.anyLamp()`
The predicate to use a specific color is: `Predicates.lampsByColor(DyeColor.DYE_COLOR)`. Where DYE_COLOR is the name of the color you want.
The predicate to use a specific color is: `Predicates.lampsByColor(DyeColor.DYE_COLOR)`. Where DYE_COLOR is the name of the color you want.

## Painted Output Buses/Hatches
For some time, Spray Paint cans could be used to paint Input and Output Buses/Hatches on multiblock machines.
Version 7.0.0 added the feature that Painted Input Buses/Hatches of different colors would not share their ingredients with
each other when the machine ran recipes. This system is more fully explained at [Painted Inputs and Outputs](../../Gameplay/Logistics/Machines.md#distinct-painted-and-filtered-inputs-and-outputs).

7.5.0 also applies this logic to Output Buses/Hatches as well. Input Buses/Hatches which have been painted are now only
allowed to send their outputs to Output Buses/Hatches that are of the same paint color, or unpainted. This may cause
existing setups to stop running, if they had painted Output Buses/hatches on them.

## Change to GTRecipe constructor
GTRecipe has had one new field added to it and its constructors: `int groupColor`. For new recipes, this parameter
is set to `-1`; however for recipes that are being prepared and run, this field holds the Paint color of the
Painted Input Group that the recipe drew its inputs from.

Addon mods which explicitly attempt to construct, copy, or apply their own ModifierFunctions to a GTRecipe
(not using the standard builder or kjs functions) will need to either add the additional `int` parameter to the end of
their constructor calls, or the additional `recipe.groupColor` to their copy calls.

11 changes: 7 additions & 4 deletions src/main/java/com/gregtechceu/gtceu/api/recipe/GTRecipe.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public class GTRecipe implements net.minecraft.world.item.crafting.Recipe<Contai
private final @NotNull EnergyStack inputEUt = calculateEUt(tickInputs);
@Getter(lazy = true)
private final @NotNull EnergyStack outputEUt = calculateEUt(tickOutputs);
public int groupColor = -1;

public GTRecipe(GTRecipeType recipeType,
Map<RecipeCapability<?>, List<Content>> inputs,
Expand All @@ -74,10 +75,11 @@ public GTRecipe(GTRecipeType recipeType,
List<?> ingredientActions,
@NotNull CompoundTag data,
int duration,
@NotNull GTRecipeCategory recipeCategory) {
@NotNull GTRecipeCategory recipeCategory,
int groupColor) {
this(recipeType, null, inputs, outputs, tickInputs, tickOutputs,
inputChanceLogics, outputChanceLogics, tickInputChanceLogics, tickOutputChanceLogics,
conditions, ingredientActions, data, duration, recipeCategory);
conditions, ingredientActions, data, duration, recipeCategory, groupColor);
}

public GTRecipe(GTRecipeType recipeType,
Expand All @@ -94,7 +96,7 @@ public GTRecipe(GTRecipeType recipeType,
List<?> ingredientActions,
@NotNull CompoundTag data,
int duration,
@NotNull GTRecipeCategory recipeCategory) {
@NotNull GTRecipeCategory recipeCategory, int groupColor) {
this.recipeType = recipeType;
this.id = id;

Expand All @@ -113,6 +115,7 @@ public GTRecipe(GTRecipeType recipeType,
this.data = data;
this.duration = duration;
this.recipeCategory = (recipeCategory != GTRecipeCategory.DEFAULT) ? recipeCategory : recipeType.getCategory();
this.groupColor = groupColor;
}

public GTRecipe copy() {
Expand All @@ -130,7 +133,7 @@ public GTRecipe copy(ContentModifier modifier, boolean modifyDuration) {
new HashMap<>(inputChanceLogics), new HashMap<>(outputChanceLogics),
new HashMap<>(tickInputChanceLogics), new HashMap<>(tickOutputChanceLogics),
new ArrayList<>(conditions),
new ArrayList<>(ingredientActions), data, duration, recipeCategory);
new ArrayList<>(ingredientActions), data, duration, recipeCategory, groupColor);
if (modifyDuration) {
copied.duration = modifier.apply(this.duration);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ public GTRecipe fromNetwork(@NotNull ResourceLocation id, @NotNull FriendlyByteB
if (data == null) {
data = new CompoundTag();
}
int groupColor = buf.readInt();
ResourceLocation categoryLoc = buf.readResourceLocation();

GTRecipeType type = (GTRecipeType) BuiltInRegistries.RECIPE_TYPE.get(recipeType);
Expand All @@ -139,7 +140,7 @@ public GTRecipe fromNetwork(@NotNull ResourceLocation id, @NotNull FriendlyByteB
GTRecipe recipe = new GTRecipe(type, id,
inputs, outputs, tickInputs, tickOutputs,
inputChanceLogics, outputChanceLogics, tickInputChanceLogics, tickOutputChanceLogics,
conditions, ingredientActions, data, duration, category);
conditions, ingredientActions, data, duration, category, groupColor);

recipe.recipeCategory.addRecipe(recipe);

Expand Down Expand Up @@ -182,6 +183,7 @@ public void toNetwork(FriendlyByteBuf buf, GTRecipe recipe) {
KJSCallWrapper.writeIngredientActions(recipe.ingredientActions, buf);
}
buf.writeNbt(recipe.data);
buf.writeInt(recipe.groupColor);
buf.writeResourceLocation(recipe.recipeCategory.registryKey);
}

Expand All @@ -205,14 +207,15 @@ private static Codec<GTRecipe> makeCodec(boolean isKubeLoaded) {
RecipeCondition.CODEC.listOf().optionalFieldOf("recipeConditions", List.of()).forGetter(val -> val.conditions),
CompoundTag.CODEC.optionalFieldOf("data", new CompoundTag()).forGetter(val -> val.data),
ExtraCodecs.NON_NEGATIVE_INT.fieldOf("duration").forGetter(val -> val.duration),
GTRegistries.RECIPE_CATEGORIES.codec().optionalFieldOf("category", GTRecipeCategory.DEFAULT).forGetter(val -> val.recipeCategory))
GTRegistries.RECIPE_CATEGORIES.codec().optionalFieldOf("category", GTRecipeCategory.DEFAULT).forGetter(val -> val.recipeCategory),
Codec.INT.fieldOf("groupColor").forGetter(val -> val.groupColor))
.apply(instance, (type,
inputs, outputs, tickInputs, tickOutputs,
inputChanceLogics, outputChanceLogics, tickInputChanceLogics, tickOutputChanceLogics,
conditions, data, duration, recipeCategory) ->
conditions, data, duration, recipeCategory, groupColor) ->
new GTRecipe(type, inputs, outputs, tickInputs, tickOutputs,
inputChanceLogics, outputChanceLogics, tickInputChanceLogics, tickOutputChanceLogics,
conditions, List.of(), data, duration, recipeCategory)));
conditions, List.of(), data, duration, recipeCategory, groupColor)));
} else {
return RecordCodecBuilder.create(instance -> instance.group(
GTRegistries.RECIPE_TYPES.codec().fieldOf("type").forGetter(val -> val.recipeType),
Expand All @@ -232,7 +235,8 @@ private static Codec<GTRecipe> makeCodec(boolean isKubeLoaded) {
KJSCallWrapper.INGREDIENT_ACTION_CODEC.optionalFieldOf("kubejs:actions", List.of()).forGetter(val -> (List<IngredientAction>) val.ingredientActions),
CompoundTag.CODEC.optionalFieldOf("data", new CompoundTag()).forGetter(val -> val.data),
ExtraCodecs.NON_NEGATIVE_INT.fieldOf("duration").forGetter(val -> val.duration),
GTRegistries.RECIPE_CATEGORIES.codec().optionalFieldOf("category", GTRecipeCategory.DEFAULT).forGetter(val -> val.recipeCategory))
GTRegistries.RECIPE_CATEGORIES.codec().optionalFieldOf("category", GTRecipeCategory.DEFAULT).forGetter(val -> val.recipeCategory),
Codec.INT.fieldOf("groupColor").forGetter(val -> val.groupColor))
.apply(instance, GTRecipe::new));
}
// spotless:on
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,10 @@ public static ActionResult handleRecipe(IRecipeCapabilityHolder holder, GTRecipe
RecipeRunner runner = new RecipeRunner(recipe, io, isTick, holder, chanceCaches, simulated);
var result = runner.handle(contents);

if (result.isSuccess() || result.capability() == null) return result;
if (result.isSuccess() || result.capability() == null) {
recipe.groupColor = runner.getGroupColor();
return result;
}

if (!simulated && ConfigHolder.INSTANCE.dev.debug) {
GTCEu.LOGGER.warn("IO {} Error while handling recipe {} outputs for {}",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
import com.gregtechceu.gtceu.api.capability.recipe.RecipeCapability;
import com.gregtechceu.gtceu.api.machine.feature.IVoidable;
import com.gregtechceu.gtceu.api.machine.trait.RecipeHandlerGroup;
import com.gregtechceu.gtceu.api.machine.trait.RecipeHandlerGroupColor;
import com.gregtechceu.gtceu.api.machine.trait.RecipeHandlerList;
import com.gregtechceu.gtceu.api.recipe.chance.boost.ChanceBoostFunction;
import com.gregtechceu.gtceu.api.recipe.chance.logic.ChanceLogic;
import com.gregtechceu.gtceu.api.recipe.content.Content;

import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
import lombok.Getter;
import org.jetbrains.annotations.NotNull;

import java.util.ArrayList;
Expand All @@ -36,6 +38,8 @@ public class RecipeRunner {
private Map<RecipeCapability<?>, List<Object>> recipeContents;
private final Map<RecipeCapability<?>, List<Object>> searchRecipeContents;
private final Predicate<RecipeCapability<?>> outputVoid;
@Getter
private int groupColor;

public RecipeRunner(GTRecipe recipe, IO io, boolean isTick,
IRecipeCapabilityHolder holder, Map<RecipeCapability<?>, Object2IntMap<?>> chanceCaches,
Expand All @@ -49,6 +53,7 @@ public RecipeRunner(GTRecipe recipe, IO io, boolean isTick,
this.searchRecipeContents = simulated ? recipeContents : new Reference2ObjectOpenHashMap<>();
this.simulated = simulated;
this.outputVoid = cap -> holder instanceof IVoidable voidable && voidable.canVoidRecipeOutputs(cap);
this.groupColor = recipe.groupColor;
}

@NotNull
Expand Down Expand Up @@ -163,6 +168,13 @@ private ActionResult handleContents() {
for (Map.Entry<RecipeHandlerGroup, List<RecipeHandlerList>> handlerListEntry : handlerGroups.entrySet()) {
if (handlerListEntry.getKey().equals(BUS_DISTINCT)) continue;

if (handlerListEntry.getKey() instanceof RecipeHandlerGroupColor coloredGroup) {
if (io == IO.IN && simulated && !isTick) {
groupColor = coloredGroup.color();
} else if (coloredGroup.color() != -1 && coloredGroup.color() != groupColor) {
continue;
}
}
// List to keep track of the remaining items for this RecipeHandlerGroup
Map<RecipeCapability<?>, List<Object>> copiedRecipeContents = searchRecipeContents;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public interface ModifierFunction {
/**
* Use this static to denote that the recipe doesn't get modified
*/
ModifierFunction IDENTITY = recipe -> recipe;
ModifierFunction IDENTITY = ModifierFunction.builder().build();

/**
* Applies this modifier to the passed recipe
Expand Down Expand Up @@ -159,7 +159,7 @@ public ModifierFunction build() {
new HashMap<>(recipe.inputChanceLogics), new HashMap<>(recipe.outputChanceLogics),
new HashMap<>(recipe.tickInputChanceLogics), new HashMap<>(recipe.tickOutputChanceLogics),
newConditions, new ArrayList<>(recipe.ingredientActions),
recipe.data, recipe.duration, recipe.recipeCategory);
recipe.data, recipe.duration, recipe.recipeCategory, recipe.groupColor);
copied.parallels = recipe.parallels * parallels;
copied.subtickParallels = recipe.subtickParallels * subtickParallels;
copied.ocLevel = recipe.ocLevel + addOCs;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ private static GTRecipe modifyOutputs(GTRecipe recipe, ContentModifier cm) {
recipe.outputChanceLogics,
recipe.tickInputChanceLogics, recipe.tickOutputChanceLogics, recipe.conditions,
recipe.ingredientActions,
recipe.data, recipe.duration, recipe.recipeCategory);
recipe.data, recipe.duration, recipe.recipeCategory, recipe.groupColor);
}

public static class DistillationTowerLogic extends RecipeLogic {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1692,7 +1692,7 @@ public GTRecipe buildRawRecipe() {
return new GTRecipe(recipeType, id.withPrefix(recipeType.registryName.getPath() + "/"),
input, output, tickInput, tickOutput,
inputChanceLogic, outputChanceLogic, tickInputChanceLogic, tickOutputChanceLogic,
conditions, List.of(), data, duration, recipeCategory);
conditions, List.of(), data, duration, recipeCategory, -1);
}

protected void warnTooManyIngredients(RecipeCapability<?> capability,
Expand Down