55import it .unimi .dsi .fastutil .ints .IntList ;
66import net .minecraft .world .level .storage .ValueInput ;
77import net .minecraft .world .level .storage .ValueOutput ;
8+ import org .apache .commons .compress .utils .Lists ;
89import org .cyclops .commoncapabilities .api .capability .recipehandler .IRecipeDefinition ;
10+ import org .cyclops .commoncapabilities .api .ingredient .IIngredientMatcher ;
911import org .cyclops .commoncapabilities .api .ingredient .IMixedIngredients ;
1012import org .cyclops .commoncapabilities .api .ingredient .IngredientComponent ;
13+ import org .cyclops .commoncapabilities .api .ingredient .MixedIngredients ;
14+ import org .cyclops .cyclopscore .ingredient .collection .IngredientList ;
15+ import org .cyclops .cyclopscore .ingredient .storage .IngredientComponentStorageSlottedCollectionWrapper ;
1116import org .cyclops .integratedcrafting .core .CraftingHelpers ;
1217import org .cyclops .integratedcrafting .core .MissingIngredients ;
1318
1419import javax .annotation .Nullable ;
20+ import java .util .Iterator ;
21+ import java .util .List ;
1522import java .util .Map ;
1623import java .util .Objects ;
1724
@@ -26,7 +33,8 @@ public class CraftingJob {
2633 private final IntList dependencyCraftingJobs ;
2734 private final IntList dependentCraftingJobs ;
2835 private int amount ;
29- private IMixedIngredients ingredientsStorage ;
36+ private IMixedIngredients ingredientsStorage ; // Total to extract from storage (simulated and immutable)
37+ private IMixedIngredients ingredientsStorageBuffer ; // The actual ingredients from storage, which are consumed over time.
3038 private Map <IngredientComponent <?, ?>, MissingIngredients <?, ?>> lastMissingIngredients ;
3139 private long startTick ;
3240 private boolean invalidInputs ;
@@ -40,6 +48,7 @@ public CraftingJob(int id, int channel, IRecipeDefinition recipe, int amount, IM
4048 this .recipe = recipe ;
4149 this .amount = amount ;
4250 this .ingredientsStorage = ingredientsStorage ;
51+ this .ingredientsStorageBuffer = new MixedIngredients (Maps .newIdentityHashMap ());
4352 this .lastMissingIngredients = Maps .newIdentityHashMap ();
4453 this .dependencyCraftingJobs = new IntArrayList ();
4554 this .dependentCraftingJobs = new IntArrayList ();
@@ -97,6 +106,85 @@ public void setIngredientsStorage(IMixedIngredients ingredientsStorage) {
97106 this .ingredientsStorage = ingredientsStorage ;
98107 }
99108
109+ public IMixedIngredients getIngredientsStorageBuffer () {
110+ return ingredientsStorageBuffer ;
111+ }
112+
113+ public void setIngredientsStorageBuffer (IMixedIngredients ingredientsStorageBuffer ) {
114+ this .ingredientsStorageBuffer = ingredientsStorageBuffer ;
115+ }
116+
117+ public <T , M > long getMissingIngredientQuantity (IngredientComponent <T , M > ingredientComponent , T instance ) {
118+ long quantityMissing = 0 ;
119+ MissingIngredients <?, ?> missingIngredients = this .lastMissingIngredients .get (ingredientComponent );
120+ if (missingIngredients != null ) {
121+ IIngredientMatcher <T , M > matcher = ingredientComponent .getMatcher ();
122+ for (MissingIngredients .Element <?, ?> element : missingIngredients .getElements ()) {
123+ for (MissingIngredients .PrototypedWithRequested <?, ?> alternative : element .getAlternatives ()) {
124+ if (matcher .matches (instance , (T ) alternative .getRequestedPrototype ().getPrototype (), matcher .withoutCondition ((M ) alternative .getRequestedPrototype ().getCondition (), ingredientComponent .getPrimaryQuantifier ().getMatchCondition ()))) {
125+ quantityMissing += alternative .getQuantityMissing ();
126+ }
127+ }
128+ }
129+ }
130+ return quantityMissing ;
131+ }
132+
133+ public <T , M > void addToIngredientsStorageBuffer (IngredientComponent <T , M > ingredientComponent , T instance ) {
134+ // Add instance to the buffer
135+ IIngredientMatcher <T , M > matcher = ingredientComponent .getMatcher ();
136+ IMixedIngredients buffer = this .getIngredientsStorageBuffer ();
137+ if (!buffer .getComponents ().contains (ingredientComponent )) {
138+ Map <IngredientComponent <?, ?>, List <?>> mixedIngredientsRaw = Maps .newIdentityHashMap ();
139+ for (IngredientComponent <?, ?> component : buffer .getComponents ()) {
140+ mixedIngredientsRaw .put (component , buffer .getInstances (component ));
141+ }
142+ List <T > list = Lists .newArrayList ();
143+ mixedIngredientsRaw .put (ingredientComponent , list );
144+ list .add (instance );
145+ buffer = new MixedIngredients (mixedIngredientsRaw );
146+ this .setIngredientsStorageBuffer (buffer );
147+ } else {
148+ List <T > instances = buffer .getInstances (ingredientComponent );
149+ if (!instances .stream ().anyMatch (matcher ::isEmpty )) {
150+ // Make sure we have at least one empty slot available, to guarantee insertion can succeed.
151+ instances .add (matcher .getEmptyInstance ());
152+ }
153+ T remaining = new IngredientComponentStorageSlottedCollectionWrapper <>(new IngredientList <>(ingredientComponent , instances ), Integer .MAX_VALUE , Integer .MAX_VALUE ).insert (instance , false );
154+ if (!matcher .isEmpty (remaining )) {
155+ throw new IllegalStateException (String .format ("Unable to insert %s into the crafting job buffer, remaining: " , instances , remaining ));
156+ }
157+ }
158+
159+ // If the instance was a missing ingredient, remove it
160+ long instanceQuantity = matcher .getQuantity (instance );
161+ MissingIngredients <?, ?> missingIngredients = this .lastMissingIngredients .get (ingredientComponent );
162+ if (missingIngredients != null ) {
163+ Iterator <? extends MissingIngredients .Element <?, ?>> it = missingIngredients .getElements ().iterator ();
164+ boolean removed = false ;
165+ while (it .hasNext () && instanceQuantity > 0 ) {
166+ MissingIngredients .Element <?, ?> element = it .next ();
167+ for (MissingIngredients .PrototypedWithRequested <?, ?> alternative : element .getAlternatives ()) {
168+ if (matcher .matches (instance , (T ) alternative .getRequestedPrototype ().getPrototype (), matcher .withoutCondition ((M ) alternative .getRequestedPrototype ().getCondition (), ingredientComponent .getPrimaryQuantifier ().getMatchCondition ()))) {
169+ long missingQuantityToConsume = Math .min (alternative .getQuantityMissing (), instanceQuantity );
170+ alternative .setQuantityMissing (alternative .getQuantityMissing () - missingQuantityToConsume );
171+ instanceQuantity -= missingQuantityToConsume ;
172+ if (alternative .getQuantityMissing () == 0 ) {
173+ removed = true ;
174+ it .remove ();
175+ }
176+ break ;
177+ }
178+ }
179+ }
180+ if (removed ) {
181+ if (missingIngredients .getElements ().isEmpty ()) {
182+ this .lastMissingIngredients .remove (ingredientComponent );
183+ }
184+ }
185+ }
186+ }
187+
100188 /**
101189 * @return The ingredients that were missing for 1 job amount. This will mostly be an empty map.
102190 */
@@ -149,6 +237,7 @@ public static void serialize(ValueOutput valueOutput, CraftingJob craftingJob) {
149237 valueOutput .putIntArray ("dependents" , craftingJob .getDependentCraftingJobs ().toIntArray ());
150238 valueOutput .putInt ("amount" , craftingJob .amount );
151239 IMixedIngredients .serialize (valueOutput .child ("ingredientsStorage" ), craftingJob .ingredientsStorage );
240+ IMixedIngredients .serialize (valueOutput .child ("ingredientsStorageBuffer" ), craftingJob .ingredientsStorageBuffer );
152241 MissingIngredients .serialize (valueOutput .child ("lastMissingIngredients" ), craftingJob .lastMissingIngredients );
153242 valueOutput .putLong ("startTick" , craftingJob .startTick );
154243 valueOutput .putBoolean ("invalidInputs" , craftingJob .invalidInputs );
@@ -164,6 +253,7 @@ public static CraftingJob deserialize(ValueInput valueInput) {
164253 IRecipeDefinition recipe = IRecipeDefinition .deserialize (valueInput .child ("recipe" ).orElseThrow ());
165254 int amount = valueInput .getInt ("amount" ).orElseThrow ();
166255 IMixedIngredients ingredientsStorage = IMixedIngredients .deserialize (valueInput .child ("ingredientsStorage" ).orElseThrow ());
256+ IMixedIngredients ingredientsStorageBuffer = valueInput .child ("ingredientsStorageBuffer" ).map (IMixedIngredients ::deserialize ).orElseGet (() -> new MixedIngredients (Maps .newIdentityHashMap ())); // TODO: rm backwards-compat in nextmajor (use orElseThrow)
167257 CraftingJob craftingJob = new CraftingJob (id , channel , recipe , amount , ingredientsStorage );
168258 for (int dependency : valueInput .getIntArray ("dependencies" ).orElseThrow ()) {
169259 craftingJob .dependencyCraftingJobs .add (dependency );
@@ -178,6 +268,7 @@ public static CraftingJob deserialize(ValueInput valueInput) {
178268 craftingJob .setInvalidInputs (valueInput .getBooleanOr ("invalidInputs" , false ));
179269 valueInput .getString ("initiatorUuid" ).ifPresent (craftingJob ::setInitiatorUuid );
180270 craftingJob .setIgnoreDependencyCheck (valueInput .getBooleanOr ("ignoreDependencyCheck" , false ));
271+ craftingJob .setIngredientsStorageBuffer (ingredientsStorageBuffer );
181272 return craftingJob ;
182273 }
183274
@@ -203,6 +294,9 @@ public boolean equals(Object obj) {
203294 }
204295
205296 public CraftingJob clone (CraftingHelpers .IIdentifierGenerator identifierGenerator ) {
297+ if (!this .getIngredientsStorageBuffer ().isEmpty ()) {
298+ throw new IllegalStateException ("Cloning a job with an ingredient buffer is illegal" );
299+ }
206300 return new CraftingJob (
207301 identifierGenerator .getNext (),
208302 getChannel (),
0 commit comments