Skip to content

Commit 6b9d576

Browse files
authored
Add slice-by-slice recipe consumption (#4006)
1 parent dd78346 commit 6b9d576

File tree

3 files changed

+452
-18
lines changed

3 files changed

+452
-18
lines changed

src/main/java/com/gregtechceu/gtceu/common/machine/multiblock/electric/AssemblyLineMachine.java

Lines changed: 192 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,23 @@
11
package com.gregtechceu.gtceu.common.machine.multiblock.electric;
22

3+
import com.gregtechceu.gtceu.GTCEu;
34
import com.gregtechceu.gtceu.api.capability.recipe.FluidRecipeCapability;
45
import com.gregtechceu.gtceu.api.capability.recipe.IO;
56
import com.gregtechceu.gtceu.api.capability.recipe.IRecipeHandler;
67
import com.gregtechceu.gtceu.api.capability.recipe.ItemRecipeCapability;
8+
import com.gregtechceu.gtceu.api.capability.recipe.RecipeCapability;
79
import com.gregtechceu.gtceu.api.machine.IMachineBlockEntity;
10+
import com.gregtechceu.gtceu.api.machine.feature.IRecipeLogicMachine;
811
import com.gregtechceu.gtceu.api.machine.feature.multiblock.IMultiPart;
912
import com.gregtechceu.gtceu.api.machine.multiblock.MultiblockControllerMachine;
1013
import com.gregtechceu.gtceu.api.machine.multiblock.WorkableElectricMultiblockMachine;
14+
import com.gregtechceu.gtceu.api.machine.trait.NotifiableFluidTank;
15+
import com.gregtechceu.gtceu.api.machine.trait.NotifiableItemStackHandler;
16+
import com.gregtechceu.gtceu.api.machine.trait.RecipeLogic;
1117
import com.gregtechceu.gtceu.api.pattern.util.RelativeDirection;
18+
import com.gregtechceu.gtceu.api.recipe.ActionResult;
1219
import com.gregtechceu.gtceu.api.recipe.GTRecipe;
20+
import com.gregtechceu.gtceu.api.recipe.RecipeHelper;
1321
import com.gregtechceu.gtceu.api.recipe.ingredient.FluidIngredient;
1422
import com.gregtechceu.gtceu.config.ConfigHolder;
1523

@@ -19,10 +27,10 @@
1927
import net.minecraft.world.item.crafting.Ingredient;
2028
import net.minecraftforge.fluids.FluidStack;
2129

30+
import it.unimi.dsi.fastutil.objects.Object2IntMap;
2231
import lombok.Getter;
2332
import lombok.experimental.Accessors;
2433
import org.jetbrains.annotations.NotNull;
25-
import org.jetbrains.annotations.Nullable;
2634

2735
import java.util.*;
2836

@@ -43,25 +51,18 @@ public AssemblyLineMachine(IMachineBlockEntity holder) {
4351
}
4452

4553
@Override
46-
public boolean beforeWorking(@Nullable GTRecipe recipe) {
47-
if (recipe == null) return false;
48-
if (!super.beforeWorking(recipe)) return false;
49-
50-
var config = ConfigHolder.INSTANCE.machines;
51-
if (!config.orderedAssemblyLineItems && !config.orderedAssemblyLineFluids) return true;
52-
if (!checkItemInputs(recipe)) return false;
53-
54-
if (!config.orderedAssemblyLineFluids) return true;
55-
return checkFluidInputs(recipe);
54+
protected RecipeLogic createRecipeLogic(Object... args) {
55+
return new AsslineRecipeLogic(this);
5656
}
5757

5858
public static Comparator<IMultiPart> partSorter(MultiblockControllerMachine mc) {
5959
return Comparator.comparing(p -> p.self().getPos(),
6060
RelativeDirection.RIGHT.getSorter(mc.getFrontFacing(), mc.getUpwardsFacing(), mc.isFlipped()));
6161
}
6262

63-
private boolean checkItemInputs(@NotNull GTRecipe recipe) {
64-
var itemInputs = recipe.inputs.getOrDefault(ItemRecipeCapability.CAP, Collections.emptyList());
63+
private boolean checkItemInputs(@NotNull GTRecipe recipe, boolean isTick) {
64+
var itemInputs = (isTick ? recipe.tickInputs : recipe.inputs).getOrDefault(ItemRecipeCapability.CAP,
65+
Collections.emptyList());
6566
if (itemInputs.isEmpty()) return true;
6667
int inputsSize = itemInputs.size();
6768
var itemHandlers = getCapabilitiesFlat(IO.IN, ItemRecipeCapability.CAP);
@@ -74,8 +75,6 @@ private boolean checkItemInputs(@NotNull GTRecipe recipe) {
7475
.map(ItemStack.class::cast)
7576
.filter(s -> !s.isEmpty())
7677
.findFirst())
77-
78-
.dropWhile(Optional::isEmpty)
7978
.limit(inputsSize)
8079
.map(o -> o.orElse(ItemStack.EMPTY))
8180
.toList();
@@ -93,8 +92,48 @@ private boolean checkItemInputs(@NotNull GTRecipe recipe) {
9392
return true;
9493
}
9594

96-
private boolean checkFluidInputs(@NotNull GTRecipe recipe) {
97-
var fluidInputs = recipe.inputs.getOrDefault(FluidRecipeCapability.CAP, Collections.emptyList());
95+
private ActionResult consumeItemContents(@NotNull GTRecipe recipe, boolean isTick) {
96+
var itemInputs = (isTick ? recipe.tickInputs : recipe.inputs).getOrDefault(ItemRecipeCapability.CAP,
97+
Collections.emptyList());
98+
if (itemInputs.isEmpty()) return ActionResult.SUCCESS;
99+
int inputsSize = itemInputs.size();
100+
var itemHandlers = getCapabilitiesFlat(IO.IN, ItemRecipeCapability.CAP);
101+
if (itemHandlers.size() < inputsSize) return ActionResult.FAIL_NO_REASON;
102+
103+
var itemInventory = itemHandlers.stream()
104+
.filter(IRecipeHandler::shouldSearchContent).toList();
105+
106+
if (itemInventory.size() < inputsSize) return ActionResult.FAIL_NO_REASON;
107+
108+
for (int i = 0; i < inputsSize; i++) {
109+
Ingredient recipeStack = ItemRecipeCapability.CAP.of(itemInputs.get(i).content);
110+
var currentBus = itemInventory.get(i);
111+
if (!(currentBus instanceof NotifiableItemStackHandler itemBus)) throw new RuntimeException(
112+
"Handler in Assline.consumeItemContent's ItemRecipeCapability.IN was not of type NotifiableItemStackHandler");
113+
var left = itemBus.handleRecipeInner(IO.IN, recipe, new ArrayList<>(List.of(recipeStack)), true);
114+
if (!(left == null || left.isEmpty())) return ActionResult.FAIL_NO_REASON;
115+
}
116+
// If we get here, the recipe should be consumable
117+
118+
for (int i = 0; i < inputsSize; i++) {
119+
Ingredient recipeStack = ItemRecipeCapability.CAP.of(itemInputs.get(i).content);
120+
var currentBus = itemInventory.get(i);
121+
if (!(currentBus instanceof NotifiableItemStackHandler itemBus)) throw new RuntimeException(
122+
"Handler in Assline.consumeItemContent's ItemRecipeCapability.IN was not of type NotifiableItemStackHandler");
123+
var left = itemBus.handleRecipeInner(IO.IN, recipe, new ArrayList<>(List.of(recipeStack)), false);
124+
if (!(left == null || left.isEmpty())) {
125+
GTCEu.LOGGER.error(
126+
"Recipe in Assline.consumeItemContents was true when simulating, but false when consuming.");
127+
return ActionResult.FAIL_NO_REASON;
128+
}
129+
}
130+
131+
return ActionResult.SUCCESS;
132+
}
133+
134+
private boolean checkFluidInputs(@NotNull GTRecipe recipe, boolean isTick) {
135+
var fluidInputs = (isTick ? recipe.tickInputs : recipe.inputs).getOrDefault(FluidRecipeCapability.CAP,
136+
Collections.emptyList());
98137
if (fluidInputs.isEmpty()) return true;
99138
int inputsSize = fluidInputs.size();
100139
var fluidHandlers = getCapabilitiesFlat(IO.IN, FluidRecipeCapability.CAP);
@@ -107,7 +146,6 @@ private boolean checkFluidInputs(@NotNull GTRecipe recipe) {
107146
.map(FluidStack.class::cast)
108147
.filter(f -> !f.isEmpty())
109148
.findFirst())
110-
.dropWhile(Optional::isEmpty)
111149
.limit(inputsSize)
112150
.map(o -> o.orElse(FluidStack.EMPTY))
113151
.toList();
@@ -123,4 +161,140 @@ private boolean checkFluidInputs(@NotNull GTRecipe recipe) {
123161
}
124162
return true;
125163
}
164+
165+
private ActionResult consumeFluidContents(@NotNull GTRecipe recipe, boolean isTick) {
166+
var fluidInputs = (isTick ? recipe.tickInputs : recipe.inputs).getOrDefault(FluidRecipeCapability.CAP,
167+
Collections.emptyList());
168+
if (fluidInputs.isEmpty()) return ActionResult.SUCCESS;
169+
int fluidsSize = fluidInputs.size();
170+
var fluidHandlers = getCapabilitiesFlat(IO.IN, FluidRecipeCapability.CAP);
171+
if (fluidHandlers.size() < fluidsSize) return ActionResult.FAIL_NO_REASON;
172+
173+
var fluidInventory = fluidHandlers.stream()
174+
.filter(IRecipeHandler::shouldSearchContent).toList();
175+
176+
if (fluidInventory.size() < fluidsSize) return ActionResult.FAIL_NO_REASON;
177+
178+
for (int i = 0; i < fluidsSize; i++) {
179+
FluidIngredient recipeStack = FluidRecipeCapability.CAP.of(fluidInputs.get(i).content);
180+
var currentBus = fluidInventory.get(i);
181+
if (!(currentBus instanceof NotifiableFluidTank fluidTank)) throw new RuntimeException(
182+
"Handler in Assline.consumeItemContent's FluidRecipeCapability.IN was not of type NotifiableFluidTank");
183+
var left = fluidTank.handleRecipeInner(IO.IN, recipe, new ArrayList<>(List.of(recipeStack)), true);
184+
if (!(left == null || left.isEmpty())) return ActionResult.FAIL_NO_REASON;
185+
}
186+
// If we get here, the recipe should be consumable
187+
188+
for (int i = 0; i < fluidsSize; i++) {
189+
FluidIngredient recipeStack = FluidRecipeCapability.CAP.of(fluidInputs.get(i).content);
190+
var currentBus = fluidInventory.get(i);
191+
if (!(currentBus instanceof NotifiableFluidTank fluidTank)) throw new RuntimeException(
192+
"Handler in Assline.consumeItemContent's FluidRecipeCapability.IN was not of type NotifiableFluidTank");
193+
var left = fluidTank.handleRecipeInner(IO.IN, recipe, new ArrayList<>(List.of(recipeStack)), false);
194+
if (!(left == null || left.isEmpty())) {
195+
GTCEu.LOGGER.error(
196+
"Recipe in Assline.consumeFluidContents was true when simulating, but false when consuming.");
197+
return ActionResult.FAIL_NO_REASON;
198+
}
199+
}
200+
201+
return ActionResult.SUCCESS;
202+
}
203+
204+
private ActionResult consumeAll(@NotNull GTRecipe recipe, boolean isTick,
205+
Map<RecipeCapability<?>, Object2IntMap<?>> chanceCaches) {
206+
GTRecipe copyWithItems = recipe.copy();
207+
copyWithItems.inputs.clear();
208+
copyWithItems.tickInputs.clear();
209+
210+
GTRecipe copyWithFluids = recipe.copy();
211+
copyWithFluids.inputs.clear();
212+
copyWithFluids.tickInputs.clear();
213+
214+
GTRecipe copyWithoutItemsFluids = recipe.copy();
215+
copyWithoutItemsFluids.inputs.clear();
216+
copyWithoutItemsFluids.tickInputs.clear();
217+
218+
for (var entry : recipe.inputs.entrySet()) {
219+
if (entry.getKey().equals(FluidRecipeCapability.CAP)) {
220+
copyWithFluids.inputs.put(entry.getKey(), entry.getValue());
221+
} else if (entry.getKey().equals(ItemRecipeCapability.CAP)) {
222+
copyWithItems.inputs.put(entry.getKey(), entry.getValue());
223+
} else {
224+
copyWithoutItemsFluids.inputs.put(entry.getKey(), entry.getValue());
225+
}
226+
}
227+
for (var entry : recipe.tickInputs.entrySet()) {
228+
if (entry.getKey().equals(FluidRecipeCapability.CAP)) {
229+
copyWithFluids.tickInputs.put(entry.getKey(), entry.getValue());
230+
} else if (entry.getKey().equals(ItemRecipeCapability.CAP)) {
231+
copyWithItems.tickInputs.put(entry.getKey(), entry.getValue());
232+
} else {
233+
copyWithoutItemsFluids.tickInputs.put(entry.getKey(), entry.getValue());
234+
}
235+
}
236+
var config = ConfigHolder.INSTANCE.machines;
237+
ActionResult result;
238+
if (config.orderedAssemblyLineItems) {
239+
result = consumeItemContents(copyWithItems, isTick);
240+
} else {
241+
result = isTick ?
242+
RecipeHelper.handleTickRecipeIO(this, copyWithItems, IO.IN, chanceCaches) :
243+
RecipeHelper.handleRecipeIO(this, copyWithItems, IO.IN, chanceCaches);
244+
}
245+
if (!result.isSuccess()) return result;
246+
247+
if (config.orderedAssemblyLineFluids) {
248+
result = consumeFluidContents(copyWithFluids, isTick);
249+
} else {
250+
result = isTick ?
251+
RecipeHelper.handleTickRecipeIO(this, copyWithFluids, IO.IN, chanceCaches) :
252+
RecipeHelper.handleRecipeIO(this, copyWithFluids, IO.IN, chanceCaches);
253+
}
254+
if (!result.isSuccess()) return result;
255+
256+
return isTick ?
257+
RecipeHelper.handleTickRecipeIO(this, copyWithoutItemsFluids, IO.IN, chanceCaches) :
258+
RecipeHelper.handleRecipeIO(this, copyWithoutItemsFluids, IO.IN, chanceCaches);
259+
}
260+
261+
class AsslineRecipeLogic extends RecipeLogic {
262+
263+
public AsslineRecipeLogic(IRecipeLogicMachine machine) {
264+
super(machine);
265+
}
266+
267+
@Override
268+
protected ActionResult handleRecipeIO(GTRecipe recipe, IO io) {
269+
if (io.equals(IO.IN)) {
270+
return consumeAll(recipe, false, this.getChanceCaches());
271+
}
272+
return RecipeHelper.handleRecipeIO(machine, recipe, io, this.chanceCaches);
273+
}
274+
275+
@Override
276+
protected ActionResult handleTickRecipeIO(GTRecipe recipe, IO io) {
277+
if (io.equals(IO.IN)) {
278+
return consumeAll(recipe, true, this.getChanceCaches());
279+
}
280+
return RecipeHelper.handleTickRecipeIO(machine, recipe, io, this.chanceCaches);
281+
}
282+
283+
@Override
284+
protected ActionResult matchRecipe(GTRecipe recipe) {
285+
// Match by normal inputs first
286+
ActionResult normalMatch = RecipeHelper.matchContents(machine, recipe);
287+
if (!normalMatch.isSuccess()) return normalMatch;
288+
289+
var config = ConfigHolder.INSTANCE.machines;
290+
if (!config.orderedAssemblyLineItems && !config.orderedAssemblyLineFluids) return ActionResult.SUCCESS;
291+
if (!checkItemInputs(recipe, false)) return ActionResult.FAIL_NO_REASON;
292+
if (!checkItemInputs(recipe, true)) return ActionResult.FAIL_NO_REASON;
293+
294+
if (!config.orderedAssemblyLineFluids) return ActionResult.SUCCESS;
295+
if (!checkFluidInputs(recipe, false)) return ActionResult.FAIL_NO_REASON;
296+
if (!checkFluidInputs(recipe, true)) return ActionResult.FAIL_NO_REASON;
297+
return ActionResult.SUCCESS;
298+
}
299+
}
126300
}

0 commit comments

Comments
 (0)