11package com .gregtechceu .gtceu .common .machine .multiblock .electric ;
22
3+ import com .gregtechceu .gtceu .GTCEu ;
34import com .gregtechceu .gtceu .api .capability .recipe .FluidRecipeCapability ;
45import com .gregtechceu .gtceu .api .capability .recipe .IO ;
56import com .gregtechceu .gtceu .api .capability .recipe .IRecipeHandler ;
67import com .gregtechceu .gtceu .api .capability .recipe .ItemRecipeCapability ;
8+ import com .gregtechceu .gtceu .api .capability .recipe .RecipeCapability ;
79import com .gregtechceu .gtceu .api .machine .IMachineBlockEntity ;
10+ import com .gregtechceu .gtceu .api .machine .feature .IRecipeLogicMachine ;
811import com .gregtechceu .gtceu .api .machine .feature .multiblock .IMultiPart ;
912import com .gregtechceu .gtceu .api .machine .multiblock .MultiblockControllerMachine ;
1013import 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 ;
1117import com .gregtechceu .gtceu .api .pattern .util .RelativeDirection ;
18+ import com .gregtechceu .gtceu .api .recipe .ActionResult ;
1219import com .gregtechceu .gtceu .api .recipe .GTRecipe ;
20+ import com .gregtechceu .gtceu .api .recipe .RecipeHelper ;
1321import com .gregtechceu .gtceu .api .recipe .ingredient .FluidIngredient ;
1422import com .gregtechceu .gtceu .config .ConfigHolder ;
1523
1927import net .minecraft .world .item .crafting .Ingredient ;
2028import net .minecraftforge .fluids .FluidStack ;
2129
30+ import it .unimi .dsi .fastutil .objects .Object2IntMap ;
2231import lombok .Getter ;
2332import lombok .experimental .Accessors ;
2433import org .jetbrains .annotations .NotNull ;
25- import org .jetbrains .annotations .Nullable ;
2634
2735import 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