diff options
Diffstat (limited to 'src/main/java/gregtech/api/recipe/check')
10 files changed, 1105 insertions, 0 deletions
diff --git a/src/main/java/gregtech/api/recipe/check/CheckRecipeResult.java b/src/main/java/gregtech/api/recipe/check/CheckRecipeResult.java new file mode 100644 index 0000000000..8af5c58f5e --- /dev/null +++ b/src/main/java/gregtech/api/recipe/check/CheckRecipeResult.java @@ -0,0 +1,54 @@ +package gregtech.api.recipe.check; + +import javax.annotation.Nonnull; + +import net.minecraft.network.PacketBuffer; + +/** + * Class to indicate the result of recipe check in the machine. It doesn't need to be actual result of recipemap check, + * but can also be status of whether to start the machine. Examples can be found at {@link CheckRecipeResultRegistry}. + * <p> + * Sample instance must be registered to {@link CheckRecipeResultRegistry}. + */ +public interface CheckRecipeResult { + + /** + * @return Unique registry ID + */ + @Nonnull + String getID(); + + /** + * @return If recipe check is successful + */ + boolean wasSuccessful(); + + /** + * @return Actual text to show on client GUI + */ + @Nonnull + String getDisplayString(); + + /** + * Create new instance to receive packet. + */ + @Nonnull + CheckRecipeResult newInstance(); + + /** + * Encode value to sync. + */ + void encode(@Nonnull PacketBuffer buffer); + + /** + * Decode synced value. + */ + void decode(PacketBuffer buffer); + + /** + * @return If this message should stay on GUI when the machine is shut down. + */ + default boolean persistsOnShutdown() { + return false; + } +} diff --git a/src/main/java/gregtech/api/recipe/check/CheckRecipeResultRegistry.java b/src/main/java/gregtech/api/recipe/check/CheckRecipeResultRegistry.java new file mode 100644 index 0000000000..dc7fc0d6a6 --- /dev/null +++ b/src/main/java/gregtech/api/recipe/check/CheckRecipeResultRegistry.java @@ -0,0 +1,170 @@ +package gregtech.api.recipe.check; + +import java.math.BigInteger; +import java.util.HashMap; +import java.util.Map; + +import javax.annotation.Nonnull; + +public final class CheckRecipeResultRegistry { + + private static final Map<String, CheckRecipeResult> registry = new HashMap<>(); + + /** + * Registers CheckRecipeResult. No duplicated IDs are allowed. + * + * @param sample Sample object to register + */ + public static void register(CheckRecipeResult sample) { + if (isRegistered(sample.getID())) { + throw new IllegalStateException( + String.format( + "ID %s is already registered for %s", + sample.getID(), + registry.get(sample.getID()) + .getClass() + .getCanonicalName())); + } + registry.put(sample.getID(), sample); + } + + public static CheckRecipeResult getSampleFromRegistry(String id) { + if (!isRegistered(id)) { + throw new RuntimeException("Unknown id: " + id); + } + return registry.get(id); + } + + public static boolean isRegistered(String id) { + return registry.containsKey(id); + } + + /** + * Successfully found recipe. + */ + @Nonnull + public static final CheckRecipeResult SUCCESSFUL = SimpleCheckRecipeResult.ofSuccess("success"); + /** + * All requirements met to generator power. + */ + @Nonnull + public static final CheckRecipeResult GENERATING = SimpleCheckRecipeResult.ofSuccess("generating"); + /** + * Cannot find recipe. + */ + @Nonnull + public static final CheckRecipeResult NO_RECIPE = SimpleCheckRecipeResult.ofFailure("no_recipe"); + /** + * Cannot process recipe because item output is full. + */ + public static final CheckRecipeResult ITEM_OUTPUT_FULL = SimpleCheckRecipeResult.ofFailure("item_output_full"); + /** + * Cannot process recipe because fluid output is full. + */ + public static final CheckRecipeResult FLUID_OUTPUT_FULL = SimpleCheckRecipeResult.ofFailure("fluid_output_full"); + /** + * Default unknown state. + */ + @Nonnull + public static final CheckRecipeResult NONE = SimpleCheckRecipeResult.ofFailure("none"); + /** + * Code crashed. + */ + public static final CheckRecipeResult CRASH = SimpleCheckRecipeResult.ofFailurePersistOnShutdown("crash"); + /** + * Cannot find valid fuel for generator. + */ + @Nonnull + public static final CheckRecipeResult NO_FUEL_FOUND = SimpleCheckRecipeResult.ofFailure("no_fuel"); + /** + * Cannot find valid turbine. + */ + @Nonnull + public static final CheckRecipeResult NO_TURBINE_FOUND = SimpleCheckRecipeResult.ofFailure("no_turbine"); + /** + * No data sticks found for Assembly Line. + */ + @Nonnull + public static final CheckRecipeResult NO_DATA_STICKS = SimpleCheckRecipeResult.ofFailure("no_data_sticks"); + /** + * EU/t overflowed. + */ + @Nonnull + public static final CheckRecipeResult POWER_OVERFLOW = SimpleCheckRecipeResult.ofFailure("power_overflow"); + /** + * Progress time overflowed. + */ + @Nonnull + public static final CheckRecipeResult DURATION_OVERFLOW = SimpleCheckRecipeResult.ofFailure("duration_overflow"); + /** + * Machine had an internal error + */ + @Nonnull + public static final CheckRecipeResult INTERNAL_ERROR = SimpleCheckRecipeResult.ofFailure("internal_error"); + /** Multiblock ore drill has no drilling fluid */ + public static final CheckRecipeResult NO_DRILLING_FLUID = SimpleCheckRecipeResult.ofFailure("no_drilling_fluid"); + /** Multiblock drill is missing mining pipe */ + public static final CheckRecipeResult MISSING_MINING_PIPE = SimpleCheckRecipeResult.ofFailure("no_mining_pipe"); + /** Concrete backfiller is out of concrete */ + public static final CheckRecipeResult BACKFILLER_NO_CONCRETE = SimpleCheckRecipeResult + .ofFailure("backfiller_no_concrete"); + + /** + * Black Hole Compressor does not have an active black hole + */ + public static final CheckRecipeResult NO_BLACK_HOLE = SimpleCheckRecipeResult.ofFailure("no_black_hole"); + /** + * Black Hole Compressor became unstable + */ + public static final CheckRecipeResult UNSTABLE_BLACK_HOLE = SimpleCheckRecipeResult + .ofFailure("unstable_black_hole"); + + public static final CheckRecipeResult NO_SEE_SKY = SimpleCheckRecipeResult.ofFailure("no_see_sky"); + + /** + * Cannot process recipe because the machine cannot handle required EUt. + */ + @Nonnull + public static CheckRecipeResult insufficientPower(long required) { + return new ResultInsufficientPower(required); + } + + /** + * Cannot process recipe because the machine cannot handle its heat. + */ + @Nonnull + public static CheckRecipeResult insufficientHeat(int required) { + return new ResultInsufficientHeat(required); + } + + /** + * Cannot process recipe because the machine is tiered and its tier is too low. + */ + @Nonnull + public static CheckRecipeResult insufficientMachineTier(int required) { + return new ResultInsufficientMachineTier(required); + } + + /** + * Cannot process recipe because the machine doesn't have enough startup power. + */ + @Nonnull + public static CheckRecipeResult insufficientStartupPower(int required) { + return new ResultInsufficientStartupPower(required); + } + + @Nonnull + public static CheckRecipeResult insufficientStartupPower(BigInteger required) { + return new ResultInsufficientStartupPowerBigInt(required); + } + + static { + register(new SimpleCheckRecipeResult(false, "", false)); + register(new ResultInsufficientPower(0)); + register(new ResultInsufficientHeat(0)); + register(new ResultInsufficientMachineTier(0)); + register(new ResultInsufficientStartupPower(0)); + register(new ResultInsufficientStartupPowerBigInt(BigInteger.ZERO)); + register(new ResultMissingItem()); + } +} diff --git a/src/main/java/gregtech/api/recipe/check/ResultInsufficientHeat.java b/src/main/java/gregtech/api/recipe/check/ResultInsufficientHeat.java new file mode 100644 index 0000000000..26c3530ba3 --- /dev/null +++ b/src/main/java/gregtech/api/recipe/check/ResultInsufficientHeat.java @@ -0,0 +1,65 @@ +package gregtech.api.recipe.check; + +import java.util.Objects; + +import javax.annotation.Nonnull; + +import net.minecraft.network.PacketBuffer; +import net.minecraft.util.StatCollector; + +import gregtech.api.enums.HeatingCoilLevel; +import gregtech.api.util.GT_Utility; + +public class ResultInsufficientHeat implements CheckRecipeResult { + + private int required; + + ResultInsufficientHeat(int required) { + this.required = required; + } + + @Override + @Nonnull + public String getID() { + return "insufficient_heat"; + } + + @Override + public boolean wasSuccessful() { + return false; + } + + @Override + @Nonnull + public String getDisplayString() { + return Objects.requireNonNull( + StatCollector.translateToLocalFormatted( + "GT5U.gui.text.insufficient_heat", + GT_Utility.formatNumbers(required), + HeatingCoilLevel.getDisplayNameFromHeat(required, true))); + } + + @Override + @Nonnull + public CheckRecipeResult newInstance() { + return new ResultInsufficientHeat(0); + } + + @Override + public void encode(@Nonnull PacketBuffer buffer) { + buffer.writeVarIntToBuffer(required); + } + + @Override + public void decode(@Nonnull PacketBuffer buffer) { + required = buffer.readVarIntFromBuffer(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ResultInsufficientHeat that = (ResultInsufficientHeat) o; + return required == that.required; + } +} diff --git a/src/main/java/gregtech/api/recipe/check/ResultInsufficientMachineTier.java b/src/main/java/gregtech/api/recipe/check/ResultInsufficientMachineTier.java new file mode 100644 index 0000000000..742eb3ef7a --- /dev/null +++ b/src/main/java/gregtech/api/recipe/check/ResultInsufficientMachineTier.java @@ -0,0 +1,63 @@ +package gregtech.api.recipe.check; + +import java.util.Objects; + +import javax.annotation.Nonnull; + +import net.minecraft.network.PacketBuffer; +import net.minecraft.util.StatCollector; + +import gregtech.api.util.GT_Utility; + +public class ResultInsufficientMachineTier implements CheckRecipeResult { + + private int required; + + ResultInsufficientMachineTier(int required) { + this.required = required; + } + + @Override + @Nonnull + public String getID() { + return "insufficient_machine_tier"; + } + + @Override + public boolean wasSuccessful() { + return false; + } + + @Override + @Nonnull + public String getDisplayString() { + return Objects.requireNonNull( + StatCollector.translateToLocalFormatted( + "GT5U.gui.text.insufficient_machine_tier", + GT_Utility.formatNumbers(required))); + } + + @Override + @Nonnull + public CheckRecipeResult newInstance() { + return new ResultInsufficientMachineTier(0); + } + + @Override + public void encode(@Nonnull PacketBuffer buffer) { + buffer.writeVarIntToBuffer(required); + } + + @Override + public void decode(@Nonnull PacketBuffer buffer) { + required = buffer.readVarIntFromBuffer(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ResultInsufficientMachineTier that = (ResultInsufficientMachineTier) o; + return required == that.required; + } +} diff --git a/src/main/java/gregtech/api/recipe/check/ResultInsufficientPower.java b/src/main/java/gregtech/api/recipe/check/ResultInsufficientPower.java new file mode 100644 index 0000000000..fdc06c0c07 --- /dev/null +++ b/src/main/java/gregtech/api/recipe/check/ResultInsufficientPower.java @@ -0,0 +1,64 @@ +package gregtech.api.recipe.check; + +import java.util.Objects; + +import javax.annotation.Nonnull; + +import net.minecraft.network.PacketBuffer; +import net.minecraft.util.StatCollector; + +import gregtech.api.util.GT_Utility; + +public class ResultInsufficientPower implements CheckRecipeResult { + + private long required; + + ResultInsufficientPower(long required) { + this.required = required; + } + + @Override + @Nonnull + public String getID() { + return "insufficient_power"; + } + + @Override + public boolean wasSuccessful() { + return false; + } + + @Override + @Nonnull + public String getDisplayString() { + return Objects.requireNonNull( + StatCollector.translateToLocalFormatted( + "GT5U.gui.text.insufficient_power", + GT_Utility.formatNumbers(required), + GT_Utility.getColoredTierNameFromVoltage(required))); + } + + @Override + @Nonnull + public CheckRecipeResult newInstance() { + return new ResultInsufficientPower(0); + } + + @Override + public void encode(@Nonnull PacketBuffer buffer) { + buffer.writeLong(required); + } + + @Override + public void decode(@Nonnull PacketBuffer buffer) { + required = buffer.readLong(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ResultInsufficientPower that = (ResultInsufficientPower) o; + return required == that.required; + } +} diff --git a/src/main/java/gregtech/api/recipe/check/ResultInsufficientStartupPower.java b/src/main/java/gregtech/api/recipe/check/ResultInsufficientStartupPower.java new file mode 100644 index 0000000000..62d2dd1fb2 --- /dev/null +++ b/src/main/java/gregtech/api/recipe/check/ResultInsufficientStartupPower.java @@ -0,0 +1,63 @@ +package gregtech.api.recipe.check; + +import java.util.Objects; + +import javax.annotation.Nonnull; + +import net.minecraft.network.PacketBuffer; +import net.minecraft.util.StatCollector; + +import gregtech.api.util.GT_Utility; + +public class ResultInsufficientStartupPower implements CheckRecipeResult { + + private int required; + + ResultInsufficientStartupPower(int required) { + this.required = required; + } + + @Override + @Nonnull + public String getID() { + return "insufficient_startup_power"; + } + + @Override + public boolean wasSuccessful() { + return false; + } + + @Override + @Nonnull + public String getDisplayString() { + return Objects.requireNonNull( + StatCollector.translateToLocalFormatted( + "GT5U.gui.text.insufficient_startup_power", + GT_Utility.formatNumbers(required))); + } + + @Override + @Nonnull + public CheckRecipeResult newInstance() { + return new ResultInsufficientStartupPower(0); + } + + @Override + public void encode(@Nonnull PacketBuffer buffer) { + buffer.writeVarIntToBuffer(required); + } + + @Override + public void decode(@Nonnull PacketBuffer buffer) { + required = buffer.readVarIntFromBuffer(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ResultInsufficientStartupPower that = (ResultInsufficientStartupPower) o; + return required == that.required; + } +} diff --git a/src/main/java/gregtech/api/recipe/check/ResultInsufficientStartupPowerBigInt.java b/src/main/java/gregtech/api/recipe/check/ResultInsufficientStartupPowerBigInt.java new file mode 100644 index 0000000000..6a719b3c9c --- /dev/null +++ b/src/main/java/gregtech/api/recipe/check/ResultInsufficientStartupPowerBigInt.java @@ -0,0 +1,59 @@ +package gregtech.api.recipe.check; + +import static util.Util.toStandardForm; + +import java.math.BigInteger; +import java.util.Objects; + +import net.minecraft.network.PacketBuffer; +import net.minecraft.util.StatCollector; + +import org.jetbrains.annotations.NotNull; + +public class ResultInsufficientStartupPowerBigInt implements CheckRecipeResult { + + private String required; + + public ResultInsufficientStartupPowerBigInt(BigInteger required) { + this.required = toStandardForm(required); + } + + @NotNull + @Override + public String getID() { + return "insufficient_startup_power_bigint"; + } + + @Override + public boolean wasSuccessful() { + return false; + } + + @NotNull + @Override + public String getDisplayString() { + return Objects.requireNonNull( + StatCollector.translateToLocalFormatted("GT5U.gui.text.insufficient_startup_power", required)); + } + + @NotNull + @Override + public CheckRecipeResult newInstance() { + return new ResultInsufficientStartupPowerBigInt(BigInteger.ZERO); + } + + @Override + public void encode(@NotNull PacketBuffer buffer) { + try { + buffer.writeStringToBuffer(required); + } catch (Exception ignored) {} + + } + + @Override + public void decode(PacketBuffer buffer) { + try { + required = buffer.readStringFromBuffer(32768); + } catch (Exception ignored) {} + } +} diff --git a/src/main/java/gregtech/api/recipe/check/ResultMissingItem.java b/src/main/java/gregtech/api/recipe/check/ResultMissingItem.java new file mode 100644 index 0000000000..868d664109 --- /dev/null +++ b/src/main/java/gregtech/api/recipe/check/ResultMissingItem.java @@ -0,0 +1,60 @@ +package gregtech.api.recipe.check; + +import java.util.Objects; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import net.minecraft.client.resources.I18n; +import net.minecraft.item.ItemStack; +import net.minecraft.network.PacketBuffer; + +import cpw.mods.fml.common.network.ByteBufUtils; + +public class ResultMissingItem implements CheckRecipeResult { + + @Nullable + public ItemStack itemStack; + + public ResultMissingItem() { + + } + + public ResultMissingItem(@Nullable ItemStack itemStack) { + this.itemStack = itemStack; + } + + @Override + @Nonnull + public String getID() { + return "missing_item"; + } + + @Override + public boolean wasSuccessful() { + return false; + } + + @Override + @Nonnull + public String getDisplayString() { + return Objects.requireNonNull( + I18n.format("GT5U.gui.text.missing_item", itemStack != null ? itemStack.getDisplayName() : "null")); + } + + @Override + @Nonnull + public CheckRecipeResult newInstance() { + return new ResultMissingItem(itemStack != null ? itemStack.copy() : null); + } + + @Override + public void encode(@Nonnull PacketBuffer buffer) { + ByteBufUtils.writeItemStack(buffer, itemStack); + } + + @Override + public void decode(PacketBuffer buffer) { + this.itemStack = ByteBufUtils.readItemStack(buffer); + } +} diff --git a/src/main/java/gregtech/api/recipe/check/SimpleCheckRecipeResult.java b/src/main/java/gregtech/api/recipe/check/SimpleCheckRecipeResult.java new file mode 100644 index 0000000000..58c85bbe9d --- /dev/null +++ b/src/main/java/gregtech/api/recipe/check/SimpleCheckRecipeResult.java @@ -0,0 +1,102 @@ +package gregtech.api.recipe.check; + +import java.util.Objects; + +import javax.annotation.Nonnull; + +import net.minecraft.network.PacketBuffer; +import net.minecraft.util.StatCollector; + +import com.gtnewhorizons.modularui.common.internal.network.NetworkUtils; + +/** + * Simple implementation of {@link CheckRecipeResult}. You can create new object without registering it. + */ +public class SimpleCheckRecipeResult implements CheckRecipeResult { + + private boolean success; + private String key; + private boolean persistsOnShutdown; + + SimpleCheckRecipeResult(boolean success, String key, boolean persistsOnShutdown) { + this.success = success; + this.key = key; + this.persistsOnShutdown = persistsOnShutdown; + } + + @Override + public String getID() { + return "simple_result"; + } + + @Override + public boolean wasSuccessful() { + return success; + } + + @Override + @Nonnull + public String getDisplayString() { + return Objects.requireNonNull(StatCollector.translateToLocal("GT5U.gui.text." + key)); + } + + @Override + @Nonnull + public CheckRecipeResult newInstance() { + return new SimpleCheckRecipeResult(false, "", false); + } + + @Override + public void encode(@Nonnull PacketBuffer buffer) { + buffer.writeBoolean(success); + NetworkUtils.writeStringSafe(buffer, key); + buffer.writeBoolean(persistsOnShutdown); + } + + @Override + public void decode(@Nonnull PacketBuffer buffer) { + success = buffer.readBoolean(); + key = NetworkUtils.readStringSafe(buffer); + persistsOnShutdown = buffer.readBoolean(); + } + + @Override + public boolean persistsOnShutdown() { + return persistsOnShutdown; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SimpleCheckRecipeResult that = (SimpleCheckRecipeResult) o; + return success == that.success && Objects.equals(key, that.key) + && persistsOnShutdown == that.persistsOnShutdown; + } + + /** + * Creates new result with successful state. Add your localized description with `GT5U.gui.text.{key}`. + * This is already registered to registry. + */ + @Nonnull + public static CheckRecipeResult ofSuccess(String key) { + return new SimpleCheckRecipeResult(true, key, false); + } + + /** + * Creates new result with failed state. Add your localized description with `GT5U.gui.text.{key}`. + * This is already registered to registry. + */ + @Nonnull + public static CheckRecipeResult ofFailure(String key) { + return new SimpleCheckRecipeResult(false, key, false); + } + + /** + * Creates new result object with failed state that does not get reset on shutdown. Add your localized description + * with `GT5U.gui.text.{key}`. This is already registered to registry. + */ + public static CheckRecipeResult ofFailurePersistOnShutdown(String key) { + return new SimpleCheckRecipeResult(false, key, true); + } +} diff --git a/src/main/java/gregtech/api/recipe/check/SingleRecipeCheck.java b/src/main/java/gregtech/api/recipe/check/SingleRecipeCheck.java new file mode 100644 index 0000000000..8683812d84 --- /dev/null +++ b/src/main/java/gregtech/api/recipe/check/SingleRecipeCheck.java @@ -0,0 +1,405 @@ +package gregtech.api.recipe.check; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Collectors; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTBase; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import net.minecraftforge.common.util.Constants; +import net.minecraftforge.fluids.Fluid; +import net.minecraftforge.fluids.FluidRegistry; +import net.minecraftforge.fluids.FluidStack; + +import com.google.common.collect.ImmutableMap; + +import gregtech.api.enums.GT_Values; +import gregtech.api.recipe.RecipeMap; +import gregtech.api.util.GT_Recipe; +import gregtech.api.util.GT_Utility; +import gregtech.api.util.GT_Utility.ItemId; + +/** + * Used by machines that are locked to a single recipe, for faster recipe check. + * <p> + * Computation time will be like these: + * <ul> + * Normal recipe check: + * <ul> + * {@link gregtech.api.recipe.FindRecipeQuery#find Find recipe from recipemap}: O(NCR) + * where N = number of machine inputs, C = average amount of recipe candidates found for specific input, + * R = computation time to {@link GT_Recipe#isRecipeInputEqual check if inputs match to recipe} + * </ul> + * <ul> + * {@link GT_Recipe#isRecipeInputEqual Check if inputs match to recipe}: O(NM) + * where N = number of machine inputs, M = number of recipe inputs + * </ul> + * </ul> + * <ul> + * {@link #checkRecipeInputs Single recipe check}: O(N + M) + * where N = number of machine inputs, M = number of recipe inputs + * </ul> + */ +public class SingleRecipeCheck { + + @Nonnull + private final GT_Recipe recipe; + @Nonnull + private final RecipeMap<?> recipeMap; + @Nonnull + private final ImmutableMap<ItemId, Integer> itemCost; + @Nonnull + private final ImmutableMap<Fluid, Integer> fluidCost; + + private final int totalItemCost; + private final int totalFluidCost; + + private SingleRecipeCheck(@Nonnull GT_Recipe recipe, @Nonnull RecipeMap<?> recipeMap, + @Nonnull ImmutableMap<ItemId, Integer> itemCost, @Nonnull ImmutableMap<Fluid, Integer> fluidCost) { + this.recipe = recipe; + this.recipeMap = recipeMap; + this.itemCost = itemCost; + this.fluidCost = fluidCost; + + this.totalItemCost = itemCost.values() + .stream() + .mapToInt(Integer::intValue) + .sum(); + this.totalFluidCost = fluidCost.values() + .stream() + .mapToInt(Integer::intValue) + .sum(); + } + + @Nonnull + public GT_Recipe getRecipe() { + return recipe; + } + + @Nonnull + public RecipeMap<?> getRecipeMap() { + return recipeMap; + } + + /** + * Returns the number of parallel recipes, or 0 if recipe is not satisfied at all. + */ + public int checkRecipeInputs(boolean consumeInputs, int maxParallel, ItemStack[] itemInputs, + FluidStack[] fluidInputs) { + int currentParallel = maxParallel; + + if (totalItemCost > 0) { + // Create map for item -> stored amount + Map<ItemId, Integer> itemMap = new HashMap<>(); + for (ItemStack itemStack : itemInputs) { + if (itemStack == null) continue; + itemMap.merge(ItemId.createNoCopy(itemStack), itemStack.stackSize, Integer::sum); + } + + // Check how many parallels can it perform for each item + for (Map.Entry<ItemId, Integer> costEntry : itemCost.entrySet()) { + currentParallel = Math + .min(currentParallel, itemMap.getOrDefault(costEntry.getKey(), 0) / costEntry.getValue()); + if (currentParallel <= 0) { + return 0; + } + } + } + + if (totalFluidCost > 0) { + // Create map for fluid -> stored amount + Map<Fluid, Integer> fluidMap = new HashMap<>(); + for (FluidStack fluidStack : fluidInputs) { + if (fluidStack == null) continue; + fluidMap.merge(fluidStack.getFluid(), fluidStack.amount, Integer::sum); + } + + // Check how many parallels can it perform for each fluid + for (Map.Entry<Fluid, Integer> costEntry : fluidCost.entrySet()) { + currentParallel = Math + .min(currentParallel, fluidMap.getOrDefault(costEntry.getKey(), 0) / costEntry.getValue()); + if (currentParallel <= 0) { + return 0; + } + } + } + + final int finalParallel = currentParallel; + if (consumeInputs) { + if (totalItemCost > 0) { + int remainingItemCost = totalItemCost * finalParallel; + Map<ItemId, Integer> runningItemCost = itemCost.entrySet() + .stream() + .collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue() * finalParallel)); + + for (ItemStack itemStack : itemInputs) { + if (itemStack == null) continue; + ItemId key = ItemId.createNoCopy(itemStack); + int runningCost = runningItemCost.getOrDefault(key, 0); + int paid = Math.min(itemStack.stackSize, runningCost); + itemStack.stackSize -= paid; + runningItemCost.put(key, runningCost - paid); + + remainingItemCost -= paid; + // If all item costs are paid, we don't need to iterate inputs furthermore + if (remainingItemCost <= 0) { + break; + } + } + } + + if (totalFluidCost > 0) { + int remainingFluidCost = totalFluidCost * finalParallel; + Map<Fluid, Integer> runningFluidCost = fluidCost.entrySet() + .stream() + .collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue() * finalParallel)); + + for (FluidStack fluidStack : fluidInputs) { + if (fluidStack == null) continue; + Fluid key = fluidStack.getFluid(); + int runningCost = runningFluidCost.getOrDefault(key, 0); + int paid = Math.min(fluidStack.amount, runningCost); + fluidStack.amount -= paid; + runningFluidCost.put(key, runningCost - paid); + + remainingFluidCost -= paid; + // If all fluid costs are paid, we don't need to iterate inputs furthermore + if (remainingFluidCost <= 0) { + break; + } + } + } + } + + return finalParallel; + } + + public NBTTagCompound writeToNBT() { + // Here we encode recipe input, output and all other important values. + // At load time we do a recipe check again, so in case the recipe is gone, we can stop tracking. + // Of course the next step would be auto migrating to new recipe (if any), but given + // we don't yet have a mean to uniquely name a recipe, this will have to make do. + // Consider move serialization code to GT_Recipe once this has been proven to work + NBTTagCompound tag = new NBTTagCompound(); + tag.setString("recipemap", recipeMap.unlocalizedName); + if (recipe.mInputs != null) { + tag.setTag("inputs", writeList(recipe.mInputs, GT_Utility::saveItem)); + } + if (recipe.mOutputs != null) { + tag.setTag("outputs", writeList(recipe.mOutputs, GT_Utility::saveItem)); + } + if (recipe.mChances != null) { + tag.setIntArray("chances", recipe.mChances); + } + if (recipe.mFluidInputs != null) { + tag.setTag(< |
