aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/gregtech/api/logic
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/gregtech/api/logic')
-rw-r--r--src/main/java/gregtech/api/logic/AbstractProcessingLogic.java346
-rw-r--r--src/main/java/gregtech/api/logic/ComplexParallelProcessingLogic.java121
-rw-r--r--src/main/java/gregtech/api/logic/ControllerFluidLogic.java152
-rw-r--r--src/main/java/gregtech/api/logic/ControllerItemLogic.java148
-rw-r--r--src/main/java/gregtech/api/logic/FluidInventoryLogic.java269
-rw-r--r--src/main/java/gregtech/api/logic/ItemInventoryLogic.java314
-rw-r--r--src/main/java/gregtech/api/logic/ModelRenderLogic.java5
-rw-r--r--src/main/java/gregtech/api/logic/MuTEProcessingLogic.java256
-rw-r--r--src/main/java/gregtech/api/logic/NullPowerLogic.java5
-rw-r--r--src/main/java/gregtech/api/logic/PowerLogic.java254
-rw-r--r--src/main/java/gregtech/api/logic/ProcessingLogic.java228
-rw-r--r--src/main/java/gregtech/api/logic/interfaces/FluidInventoryLogicHost.java95
-rw-r--r--src/main/java/gregtech/api/logic/interfaces/ItemInventoryLogicHost.java172
-rw-r--r--src/main/java/gregtech/api/logic/interfaces/ModelRenderLogicHost.java10
-rw-r--r--src/main/java/gregtech/api/logic/interfaces/PowerLogicHost.java60
-rw-r--r--src/main/java/gregtech/api/logic/interfaces/ProcessingLogicHost.java82
16 files changed, 2517 insertions, 0 deletions
diff --git a/src/main/java/gregtech/api/logic/AbstractProcessingLogic.java b/src/main/java/gregtech/api/logic/AbstractProcessingLogic.java
new file mode 100644
index 0000000000..ae78bbacc2
--- /dev/null
+++ b/src/main/java/gregtech/api/logic/AbstractProcessingLogic.java
@@ -0,0 +1,346 @@
+package gregtech.api.logic;
+
+import java.util.function.Supplier;
+
+import javax.annotation.Nonnull;
+
+import net.minecraft.item.ItemStack;
+import net.minecraftforge.fluids.FluidStack;
+
+import gregtech.api.interfaces.tileentity.IVoidable;
+import gregtech.api.recipe.RecipeMap;
+import gregtech.api.recipe.check.CheckRecipeResult;
+import gregtech.api.recipe.check.CheckRecipeResultRegistry;
+import gregtech.api.util.GT_OverclockCalculator;
+import gregtech.api.util.GT_ParallelHelper;
+import gregtech.api.util.GT_Recipe;
+
+/**
+ * Logic class to calculate result of recipe check from inputs.
+ */
+@SuppressWarnings({ "unused", "UnusedReturnValue" })
+public abstract class AbstractProcessingLogic<P extends AbstractProcessingLogic<P>> {
+
+ protected IVoidable machine;
+ protected Supplier<RecipeMap<?>> recipeMapSupplier;
+ protected GT_Recipe lastRecipe;
+ protected RecipeMap<?> lastRecipeMap;
+ protected ItemStack[] outputItems;
+ protected FluidStack[] outputFluids;
+ protected long calculatedEut;
+ protected int duration;
+ protected long availableVoltage;
+ protected long availableAmperage;
+ protected int overClockTimeReduction = 1;
+ protected int overClockPowerIncrease = 2;
+ protected boolean protectItems;
+ protected boolean protectFluids;
+ protected int maxParallel = 1;
+ protected Supplier<Integer> maxParallelSupplier;
+ protected int calculatedParallels = 0;
+ protected int batchSize = 1;
+ protected float euModifier = 1.0f;
+ protected float speedBoost = 1.0f;
+ protected boolean amperageOC = true;
+ protected boolean isCleanroom;
+
+ // #region Setters
+
+ /**
+ * Overwrites item output result of the calculation.
+ */
+ public P setOutputItems(ItemStack... itemOutputs) {
+ this.outputItems = itemOutputs;
+ return getThis();
+ }
+
+ /**
+ * Overwrites fluid output result of the calculation.
+ */
+ public P setOutputFluids(FluidStack... fluidOutputs) {
+ this.outputFluids = fluidOutputs;
+ return getThis();
+ }
+
+ public P setIsCleanroom(boolean isCleanroom) {
+ this.isCleanroom = isCleanroom;
+ return getThis();
+ }
+
+ /**
+ * Sets max amount of parallel.
+ */
+ public P setMaxParallel(int maxParallel) {
+ this.maxParallel = maxParallel;
+ return getThis();
+ }
+
+ /**
+ * Sets method to get max amount of parallel.
+ */
+ public P setMaxParallelSupplier(Supplier<Integer> supplier) {
+ this.maxParallelSupplier = supplier;
+ return getThis();
+ }
+
+ /**
+ * Sets batch size for batch mode.
+ */
+ public P setBatchSize(int size) {
+ this.batchSize = size;
+ return getThis();
+ }
+
+ public P setRecipeMap(RecipeMap<?> recipeMap) {
+ return setRecipeMapSupplier(() -> recipeMap);
+ }
+
+ public P setRecipeMapSupplier(Supplier<RecipeMap<?>> supplier) {
+ this.recipeMapSupplier = supplier;
+ return getThis();
+ }
+
+ public P setEuModifier(float modifier) {
+ this.euModifier = modifier;
+ return getThis();
+ }
+
+ public P setSpeedBonus(float speedModifier) {
+ this.speedBoost = speedModifier;
+ return getThis();
+ }
+
+ /**
+ * Sets machine used for void protection logic.
+ */
+ public P setMachine(IVoidable machine) {
+ this.machine = machine;
+ return getThis();
+ }
+
+ /**
+ * Overwrites duration result of the calculation.
+ */
+ public P setDuration(int duration) {
+ this.duration = duration;
+ return getThis();
+ }
+
+ /**
+ * Overwrites EU/t result of the calculation.
+ */
+ public P setCalculatedEut(long calculatedEut) {
+ this.calculatedEut = calculatedEut;
+ return getThis();
+ }
+
+ /**
+ * Sets voltage of the machine. It doesn't need to be actual voltage (excluding amperage) of the machine;
+ * For example, most of the multiblock machines set maximum possible input power (including amperage) as voltage
+ * and 1 as amperage. That way recipemap search will be executed with overclocked voltage.
+ */
+ public P setAvailableVoltage(long voltage) {
+ availableVoltage = voltage;
+ return getThis();
+ }
+
+ /**
+ * Sets amperage of the machine. This amperage doesn't involve in EU/t when searching recipemap.
+ * Useful for preventing tier skip but still considering amperage for parallel.
+ */
+ public P setAvailableAmperage(long amperage) {
+ availableAmperage = amperage;
+ return getThis();
+ }
+
+ public P setVoidProtection(boolean protectItems, boolean protectFluids) {
+ this.protectItems = protectItems;
+ this.protectFluids = protectFluids;
+ return getThis();
+ }
+
+ /**
+ * Sets custom overclock ratio. 2/4 by default.
+ * Parameters represent number of bit shift, so 1 -> 2x, 2 -> 4x.
+ */
+ public P setOverclock(int timeReduction, int powerIncrease) {
+ this.overClockTimeReduction = timeReduction;
+ this.overClockPowerIncrease = powerIncrease;
+ return getThis();
+ }
+
+ /**
+ * Sets overclock ratio to 4/4.
+ */
+ public P enablePerfectOverclock() {
+ return this.setOverclock(2, 2);
+ }
+
+ /**
+ * Sets whether the multi should use amperage to OC or not
+ */
+ public P setAmperageOC(boolean amperageOC) {
+ this.amperageOC = amperageOC;
+ return getThis();
+ }
+
+ /**
+ * Clears calculated results (and provided machine inputs) to prepare for the next machine operation.
+ */
+ public P clear() {
+ this.calculatedEut = 0;
+ this.duration = 0;
+ this.calculatedParallels = 0;
+ return getThis();
+ }
+
+ // #endregion
+
+ // #region Logic
+
+ /**
+ * Executes the recipe check: Find recipe from recipemap, Calculate parallel, overclock and outputs.
+ */
+ @Nonnull
+ public abstract CheckRecipeResult process();
+
+ /**
+ * Refreshes recipemap to use. Remember to call this before {@link #process} to make sure correct recipemap is used.
+ *
+ * @return Recipemap to use now
+ */
+ protected RecipeMap<?> preProcess() {
+ RecipeMap<?> recipeMap;
+ if (recipeMapSupplier == null) {
+ recipeMap = null;
+ } else {
+ recipeMap = recipeMapSupplier.get();
+ }
+ if (lastRecipeMap != recipeMap) {
+ lastRecipe = null;
+ lastRecipeMap = recipeMap;
+ }
+
+ if (maxParallelSupplier != null) {
+ maxParallel = maxParallelSupplier.get();
+ }
+
+ return recipeMap;
+ }
+
+ /**
+ * Check has been succeeded, so it applies the recipe and calculated parameters.
+ * At this point, inputs have been already consumed.
+ */
+ @Nonnull
+ protected CheckRecipeResult applyRecipe(@Nonnull GT_Recipe recipe, @Nonnull GT_ParallelHelper helper,
+ @Nonnull GT_OverclockCalculator calculator, @Nonnull CheckRecipeResult result) {
+ if (recipe.mCanBeBuffered) {
+ lastRecipe = recipe;
+ } else {
+ lastRecipe = null;
+ }
+ calculatedParallels = helper.getCurrentParallel();
+
+ if (calculator.getConsumption() == Long.MAX_VALUE) {
+ return CheckRecipeResultRegistry.POWER_OVERFLOW;
+ }
+ if (calculator.getDuration() == Integer.MAX_VALUE) {
+ return CheckRecipeResultRegistry.DURATION_OVERFLOW;
+ }
+
+ calculatedEut = calculator.getConsumption();
+
+ double finalDuration = calculateDuration(recipe, helper, calculator);
+ if (finalDuration >= Integer.MAX_VALUE) {
+ return CheckRecipeResultRegistry.DURATION_OVERFLOW;
+ }
+ duration = (int) finalDuration;
+
+ CheckRecipeResult hookResult = onRecipeStart(recipe);
+ if (!hookResult.wasSuccessful()) {
+ return hookResult;
+ }
+
+ outputItems = helper.getItemOutputs();
+ outputFluids = helper.getFluidOutputs();
+
+ return result;
+ }
+
+ /**
+ * Override to tweak final duration that will be set as a result of this logic class.
+ */
+ protected double calculateDuration(@Nonnull GT_Recipe recipe, @Nonnull GT_ParallelHelper helper,
+ @Nonnull GT_OverclockCalculator calculator) {
+ return calculator.getDuration() * helper.getDurationMultiplierDouble();
+ }
+
+ /**
+ * Override to do additional check for found recipe if needed.
+ */
+ @Nonnull
+ protected CheckRecipeResult validateRecipe(@Nonnull GT_Recipe recipe) {
+ return CheckRecipeResultRegistry.SUCCESSFUL;
+ }
+
+ /**
+ * Override to perform additional logic when recipe starts.
+ *
+ * This is called when the recipe processing logic has finished all
+ * checks, consumed all inputs, but has not yet set the outputs to
+ * be produced. Returning a result other than SUCCESSFUL will void
+ * all inputs!
+ */
+ @Nonnull
+ protected CheckRecipeResult onRecipeStart(@Nonnull GT_Recipe recipe) {
+ return CheckRecipeResultRegistry.SUCCESSFUL;
+ }
+
+ /**
+ * Override to tweak overclock logic if needed.
+ */
+ @Nonnull
+ protected GT_OverclockCalculator createOverclockCalculator(@Nonnull GT_Recipe recipe) {
+ return new GT_OverclockCalculator().setRecipeEUt(recipe.mEUt)
+ .setAmperage(availableAmperage)
+ .setEUt(availableVoltage)
+ .setDuration(recipe.mDuration)
+ .setSpeedBoost(speedBoost)
+ .setEUtDiscount(euModifier)
+ .setAmperageOC(amperageOC)
+ .setDurationDecreasePerOC(overClockTimeReduction)
+ .setEUtIncreasePerOC(overClockPowerIncrease);
+ }
+ // #endregion
+
+ // #region Getters
+
+ public ItemStack[] getOutputItems() {
+ return outputItems;
+ }
+
+ public FluidStack[] getOutputFluids() {
+ return outputFluids;
+ }
+
+ public int getDuration() {
+ return duration;
+ }
+
+ public long getCalculatedEut() {
+ return calculatedEut;
+ }
+
+ public int getCurrentParallels() {
+ return calculatedParallels;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Nonnull
+ public P getThis() {
+ return (P) this;
+ }
+
+ // #endregion
+}
diff --git a/src/main/java/gregtech/api/logic/ComplexParallelProcessingLogic.java b/src/main/java/gregtech/api/logic/ComplexParallelProcessingLogic.java
new file mode 100644
index 0000000000..72a74ebd04
--- /dev/null
+++ b/src/main/java/gregtech/api/logic/ComplexParallelProcessingLogic.java
@@ -0,0 +1,121 @@
+package gregtech.api.logic;
+
+import java.util.stream.LongStream;
+
+import net.minecraft.item.ItemStack;
+import net.minecraftforge.fluids.FluidStack;
+
+public class ComplexParallelProcessingLogic<P extends ComplexParallelProcessingLogic<P>>
+ extends MuTEProcessingLogic<P> {
+
+ protected int maxComplexParallels;
+ protected ItemStack[][] outputItems;
+ protected FluidStack[][] outputFluids;
+ protected long[] calculatedEutValues;
+ protected int[] durations;
+ protected int[] progresses;
+
+ public P setMaxComplexParallel(int maxComplexParallels) {
+ this.maxComplexParallels = maxComplexParallels;
+ reinitializeProcessingArrays();
+ return getThis();
+ }
+
+ public ItemStack[] getOutputItems(int index) {
+ if (index >= 0 && index < maxComplexParallels) {
+ return outputItems[index];
+ }
+ return null;
+ }
+
+ public FluidStack[] getOutputFluids(int index) {
+ if (index >= 0 && index < maxComplexParallels) {
+ return outputFluids[index];
+ }
+ return null;
+ }
+
+ @Override
+ public boolean canWork() {
+ for (int i = 0; i < maxComplexParallels; i++) {
+ if (progresses[i] >= durations[i]) {
+ return machineHost.isAllowedToWork();
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public long getCalculatedEut() {
+ return LongStream.of(this.calculatedEutValues)
+ .sum();
+ }
+
+ public int getDuration(int index) {
+ return durations[index];
+ }
+
+ public int getProgress(int index) {
+ return progresses[index];
+ }
+
+ @Override
+ public void progress() {
+ for (int i = 0; i < maxComplexParallels; i++) {
+ if (progresses[i] == durations[i]) {
+ progresses[i] = 0;
+ durations[i] = 0;
+ output(i);
+ continue;
+ }
+ progresses[i] = progresses[i] + 1;
+ }
+ }
+
+ @Override
+ public void startCheck() {
+ for (int i = 0; i < maxComplexParallels; i++) {
+ if (durations[i] > 0) continue;
+ recipeResult = process();
+ calculatedEutValues[i] = calculatedEut;
+ durations[i] = duration;
+ progresses[i] = 0;
+ outputItems[i] = getOutputItems();
+ outputFluids[i] = getOutputFluids();
+ }
+ }
+
+ protected void output(int index) {
+ setOutputItems(getOutputItems(index));
+ setOutputFluids(getOutputFluids(index));
+ output();
+ }
+
+ protected void reinitializeProcessingArrays() {
+ ItemStack[][] oldOutputItems = outputItems;
+ FluidStack[][] oldOutputFluids = outputFluids;
+ long[] oldCalculatedEutValues = calculatedEutValues;
+ int[] oldDurations = durations;
+ int[] oldProgresses = progresses;
+ outputItems = new ItemStack[maxComplexParallels][];
+ outputFluids = new FluidStack[maxComplexParallels][];
+ calculatedEutValues = new long[maxComplexParallels];
+ durations = new int[maxComplexParallels];
+ progresses = new int[maxComplexParallels];
+ for (int i = 0; i < oldOutputItems.length; i++) {
+ outputItems[i] = oldOutputItems[i];
+ }
+ for (int i = 0; i < oldOutputFluids.length; i++) {
+ outputFluids[i] = oldOutputFluids[i];
+ }
+ for (int i = 0; i < oldCalculatedEutValues.length; i++) {
+ calculatedEutValues[i] = oldCalculatedEutValues[i];
+ }
+ for (int i = 0; i < oldDurations[i]; i++) {
+ durations[i] = oldDurations[i];
+ }
+ for (int i = 0; i < oldProgresses.length; i++) {
+ progresses[i] = oldProgresses[i];
+ }
+ }
+}
diff --git a/src/main/java/gregtech/api/logic/ControllerFluidLogic.java b/src/main/java/gregtech/api/logic/ControllerFluidLogic.java
new file mode 100644
index 0000000000..211c1c2982
--- /dev/null
+++ b/src/main/java/gregtech/api/logic/ControllerFluidLogic.java
@@ -0,0 +1,152 @@
+package gregtech.api.logic;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Set;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.nbt.NBTTagList;
+import net.minecraftforge.common.util.Constants;
+
+import org.apache.commons.lang3.tuple.Pair;
+
+/**
+ * Controller logic for Fluid inventories
+ *
+ * @author BlueWeabo
+ */
+public class ControllerFluidLogic {
+
+ private final Map<UUID, FluidInventoryLogic> inventories = new HashMap<>();
+ private final Set<Pair<UUID, FluidInventoryLogic>> unallocatedInventories = new HashSet<>();
+
+ public void addInventory(@Nonnull UUID id, @Nonnull FluidInventoryLogic inventory) {
+ Pair<UUID, FluidInventoryLogic> found = checkIfInventoryExistsAsUnallocated(inventory);
+ if (inventory.isUpgradeInventory() && found != null) {
+ unallocatedInventories.remove(found);
+ inventories.put(id, found.getRight());
+ return;
+ }
+ inventories.put(id, inventory);
+ }
+
+ @Nonnull
+ public UUID addInventory(@Nonnull FluidInventoryLogic inventory) {
+ Pair<UUID, FluidInventoryLogic> found = checkIfInventoryExistsAsUnallocated(inventory);
+ if (inventory.isUpgradeInventory() && found != null) {
+ unallocatedInventories.remove(found);
+ inventories.put(found.getLeft(), found.getRight());
+ return Objects.requireNonNull(found.getLeft());
+ }
+ UUID generatedUUID = Objects.requireNonNull(UUID.randomUUID());
+ inventories.put(generatedUUID, inventory);
+ return generatedUUID;
+ }
+
+ @Nullable
+ private Pair<UUID, FluidInventoryLogic> checkIfInventoryExistsAsUnallocated(
+ @Nonnull FluidInventoryLogic inventory) {
+ if (unallocatedInventories.size() == 0) {
+ return null;
+ }
+ return unallocatedInventories.stream()
+ .filter(
+ unallocated -> unallocated.getRight()
+ .getTier() == inventory.getTier())
+ .findFirst()
+ .get();
+ }
+
+ /**
+ * Removes the inventory with said id and gives it back to be processed if needed.
+ */
+ @Nonnull
+ public FluidInventoryLogic removeInventory(@Nonnull UUID id) {
+ return Objects.requireNonNull(inventories.remove(id));
+ }
+
+ @Nonnull
+ public FluidInventoryLogic getAllInventoryLogics() {
+ return new FluidInventoryLogic(
+ inventories.values()
+ .stream()
+ .map(inv -> inv.getInventory())
+ .collect(Collectors.toList()));
+ }
+
+ @Nonnull
+ public FluidInventoryLogic getInventoryLogic(@Nullable UUID id) {
+ if (id == null) return getAllInventoryLogics();
+ return Objects.requireNonNull(inventories.getOrDefault(id, getAllInventoryLogics()));
+ }
+
+ @Nonnull
+ public Set<Entry<UUID, FluidInventoryLogic>> getAllInventoryLogicsAsEntrySet() {
+ return Objects.requireNonNull(inventories.entrySet());
+ }
+
+ @Nonnull
+ public String getInventoryDisplayName(@Nullable UUID id) {
+ if (id == null) return "";
+ FluidInventoryLogic logic = inventories.get(id);
+ if (logic == null) return "";
+ String displayName = logic.getDisplayName();
+ if (displayName == null) return Objects.requireNonNull(id.toString());
+ return displayName;
+ }
+
+ public void setInventoryDisplayName(@Nullable UUID id, @Nullable String displayName) {
+ if (id == null) return;
+ FluidInventoryLogic logic = inventories.get(id);
+ if (logic == null) return;
+ logic.setDisplayName(displayName);
+ }
+
+ @Nonnull
+ public NBTTagCompound saveToNBT() {
+ NBTTagCompound nbt = new NBTTagCompound();
+ NBTTagList inventoriesNBT = new NBTTagList();
+ inventories.forEach((uuid, inventory) -> {
+ NBTTagCompound inventoryNBT = new NBTTagCompound();
+ inventoryNBT.setTag("inventory", inventory.saveToNBT());
+ inventoryNBT.setString("uuid", uuid.toString());
+ inventoryNBT.setInteger(
+ "invSize",
+ inventory.getInventory()
+ .getTanks());
+ inventoryNBT.setLong(
+ "tankCapacity",
+ inventory.getInventory()
+ .getTankCapacity(0));
+ inventoriesNBT.appendTag(inventoryNBT);
+ });
+ nbt.setTag("inventories", inventoriesNBT);
+ return nbt;
+ }
+
+ public void loadFromNBT(@Nonnull NBTTagCompound nbt) {
+ NBTTagList inventoriesNBT = nbt.getTagList("inventories", Constants.NBT.TAG_COMPOUND);
+ if (inventoriesNBT == null) return;
+ for (int i = 0; i < inventoriesNBT.tagCount(); i++) {
+ NBTTagCompound inventoryNBT = inventoriesNBT.getCompoundTagAt(i);
+ UUID uuid = UUID.fromString(inventoryNBT.getString("uuid"));
+ FluidInventoryLogic inventory = new FluidInventoryLogic(
+ inventoryNBT.getInteger("invSize"),
+ inventoryNBT.getLong("tankCapacity"));
+ inventory.loadFromNBT(inventoryNBT.getCompoundTag("inventory"));
+ if (inventory.isUpgradeInventory()) {
+ unallocatedInventories.add(Pair.of(uuid, inventory));
+ } else {
+ inventories.put(uuid, inventory);
+ }
+ }
+ }
+}
diff --git a/src/main/java/gregtech/api/logic/ControllerItemLogic.java b/src/main/java/gregtech/api/logic/ControllerItemLogic.java
new file mode 100644
index 0000000000..2863c2f49c
--- /dev/null
+++ b/src/main/java/gregtech/api/logic/ControllerItemLogic.java
@@ -0,0 +1,148 @@
+package gregtech.api.logic;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Set;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.nbt.NBTTagList;
+import net.minecraftforge.common.util.Constants;
+
+import org.apache.commons.lang3.tuple.Pair;
+
+/**
+ * Logic of the Item logic for the controller. This is controlling all of the inventories.
+ *
+ * @author BlueWeabo
+ */
+public class ControllerItemLogic {
+
+ private final Map<UUID, ItemInventoryLogic> inventories = new HashMap<>();
+ private final Set<Pair<UUID, ItemInventoryLogic>> unallocatedInventories = new HashSet<>();
+
+ public void addInventory(@Nonnull UUID id, @Nonnull ItemInventoryLogic inventory) {
+ Pair<UUID, ItemInventoryLogic> found = checkIfInventoryExistsAsUnallocated(inventory);
+ if (inventory.isUpgradeInventory() && found != null) {
+ unallocatedInventories.remove(found);
+ inventories.put(id, found.getRight());
+ return;
+ }
+ inventories.put(id, inventory);
+ }
+
+ @Nonnull
+ public UUID addInventory(@Nonnull ItemInventoryLogic inventory) {
+ Pair<UUID, ItemInventoryLogic> found = checkIfInventoryExistsAsUnallocated(inventory);
+ if (inventory.isUpgradeInventory() && found != null) {
+ unallocatedInventories.remove(found);
+ inventories.put(found.getLeft(), found.getRight());
+ return Objects.requireNonNull(found.getLeft());
+ }
+ UUID generatedUUID = Objects.requireNonNull(UUID.randomUUID());
+ inventories.put(generatedUUID, inventory);
+ return generatedUUID;
+ }
+
+ @Nullable
+ private Pair<UUID, ItemInventoryLogic> checkIfInventoryExistsAsUnallocated(@Nonnull ItemInventoryLogic inventory) {
+ if (unallocatedInventories.size() == 0) {
+ return null;
+ }
+ return unallocatedInventories.stream()
+ .filter(
+ unallocated -> unallocated.getRight()
+ .getTier() == inventory.getTier()
+ && unallocated.getRight()
+ .getSlots() == inventory.getSlots())
+ .findFirst()
+ .get();
+ }
+
+ /**
+ * Removes the inventory with said id and gives it back to be processed if needed.
+ */
+ @Nonnull
+ public ItemInventoryLogic removeInventory(@Nonnull UUID id) {
+ return Objects.requireNonNull(inventories.remove(id));
+ }
+
+ @Nonnull
+ public ItemInventoryLogic getAllInventoryLogics() {
+ return new ItemInventoryLogic(
+ inventories.values()
+ .stream()
+ .map(inv -> inv.getInventory())
+ .collect(Collectors.toList()));
+ }
+
+ @Nonnull
+ public ItemInventoryLogic getInventoryLogic(@Nullable UUID id) {
+ if (id == null) return getAllInventoryLogics();
+ return Objects.requireNonNull(inventories.getOrDefault(id, getAllInventoryLogics()));
+ }
+
+ @Nonnull
+ public Set<Entry<UUID, ItemInventoryLogic>> getAllInventoryLogicsAsEntrySet() {
+ return Objects.requireNonNull(inventories.entrySet());
+ }
+
+ @Nullable
+ public String getInventoryDisplayName(@Nullable UUID id) {
+ if (id == null) return "";
+ ItemInventoryLogic logic = inventories.get(id);
+ if (logic == null) return "";
+ String displayName = logic.getDisplayName();
+ if (displayName == null) return Objects.requireNonNull(id.toString());
+ return displayName;
+ }
+
+ public void setInventoryDisplayName(@Nullable UUID id, @Nullable String displayName) {
+ if (id == null) return;
+ ItemInventoryLogic logic = inventories.get(id);
+ if (logic == null) return;
+ logic.setDisplayName(displayName);
+ }
+
+ @Nonnull
+ public NBTTagCompound saveToNBT() {
+ NBTTagCompound nbt = new NBTTagCompound();
+ NBTTagList inventoriesNBT = new NBTTagList();
+ inventories.forEach((uuid, inventory) -> {
+ NBTTagCompound inventoryNBT = new NBTTagCompound();
+ inventoryNBT.setTag("inventory", inventory.saveToNBT());
+ inventoryNBT.setString("uuid", uuid.toString());
+ inventoryNBT.setInteger(
+ "invSize",
+ inventory.getInventory()
+ .getSlots());
+ inventoriesNBT.appendTag(inventoryNBT);
+ });
+ nbt.setTag("inventories", inventoriesNBT);
+ return nbt;
+ }
+
+ public void loadFromNBT(@Nonnull NBTTagCompound nbt) {
+ NBTTagList inventoriesNBT = nbt.getTagList("inventories", Constants.NBT.TAG_COMPOUND);
+ if (inventoriesNBT == null) return;
+ for (int i = 0; i < inventoriesNBT.tagCount(); i++) {
+ NBTTagCompound inventoryNBT = inventoriesNBT.getCompoundTagAt(i);
+ UUID uuid = UUID.fromString(inventoryNBT.getString("uuid"));
+ ItemInventoryLogic inventory = new ItemInventoryLogic(inventoryNBT.getInteger("invSize"));
+ NBTTagCompound internalInventoryNBT = inventoryNBT.getCompoundTag("inventory");
+ if (internalInventoryNBT != null) inventory.loadFromNBT(internalInventoryNBT);
+ if (inventory.isUpgradeInventory()) {
+ unallocatedInventories.add(Pair.of(uuid, inventory));
+ } else {
+ inventories.put(uuid, inventory);
+ }
+ }
+ }
+}
diff --git a/src/main/java/gregtech/api/logic/FluidInventoryLogic.java b/src/main/java/gregtech/api/logic/FluidInventoryLogic.java
new file mode 100644
index 0000000000..88c0c954ec
--- /dev/null
+++ b/src/main/java/gregtech/api/logic/FluidInventoryLogic.java
@@ -0,0 +1,269 @@
+package gregtech.api.logic;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+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.FluidStack;
+
+import com.gtnewhorizons.modularui.api.fluids.FluidTanksHandler;
+import com.gtnewhorizons.modularui.api.fluids.IFluidTankLong;
+import com.gtnewhorizons.modularui.api.fluids.IFluidTanksHandler;
+import com.gtnewhorizons.modularui.api.fluids.ListFluidHandler;
+import com.gtnewhorizons.modularui.api.math.Size;
+import com.gtnewhorizons.modularui.api.widget.Widget;
+import com.gtnewhorizons.modularui.common.widget.FluidSlotWidget;
+import com.gtnewhorizons.modularui.common.widget.Scrollable;
+
+/**
+ * Generic Fluid logic for MuTEs.
+ *
+ * @author BlueWeabo
+ */
+public class FluidInventoryLogic {
+
+ private static final int DEFAULT_COLUMNS_PER_ROW = 4;
+ private static final int POSITION_INTERVAL = 18;
+ private static final Size SIZE = new Size(18, 18);
+
+ protected String displayName = "";
+ @Nonnull
+ protected final IFluidTanksHandler inventory;
+ protected final Map<Fluid, IFluidTankLong> fluidToTankMap;
+ protected int tier = 0;
+ protected boolean isUpgradeInventory = false;
+
+ public FluidInventoryLogic(int numberOfSlots, long capacityOfEachTank) {
+ this(new FluidTanksHandler(numberOfSlots, capacityOfEachTank), 0, false);
+ }
+
+ public FluidInventoryLogic(int numberOfSlots, long capacityOfEachTank, int tier) {
+ this(new FluidTanksHandler(numberOfSlots, capacityOfEachTank), tier, false);
+ }
+
+ public FluidInventoryLogic(int numberOfSlots, long capacityOfEachTank, int tier, boolean isUpgradeInventory) {
+ this(new FluidTanksHandler(numberOfSlots, capacityOfEachTank), tier, isUpgradeInventory);
+ }
+
+ public FluidInventoryLogic(@Nonnull IFluidTanksHandler inventory, int tier, boolean isUpgradeInventory) {
+ this.inventory = inventory;
+ fluidToTankMap = new HashMap<>(inventory.getTanks());
+ this.tier = tier;
+ this.isUpgradeInventory = isUpgradeInventory;
+ }
+
+ public FluidInventoryLogic(Collection<IFluidTanksHandler> inventories) {
+ this(new ListFluidHandler(inventories), -1, false);
+ }
+
+ @Nullable
+ public String getDisplayName() {
+ return displayName;
+ }
+
+ public int getTier() {
+ return tier;
+ }
+
+ public boolean isUpgradeInventory() {
+ return isUpgradeInventory;
+ }
+
+ public void setDisplayName(@Nullable String displayName) {
+ this.displayName = displayName;
+ }
+
+ /**
+ *
+ * @return The Fluid Inventory Logic as an NBTTagList to be saved in another nbt as how one wants.
+ */
+ @Nonnull
+ public NBTTagCompound saveToNBT() {
+ final NBTTagCompound nbt = new NBTTagCompound();
+ final NBTTagList tList = new NBTTagList();
+ for (int tankNumber = 0; tankNumber < inventory.getTanks(); tankNumber++) {
+ final IFluidTankLong tank = inventory.getFluidTank(tankNumber);
+ if (tank == null) continue;
+
+ final NBTTagCompound tag = new NBTTagCompound();
+ tag.setByte("s", (byte) tankNumber);
+ tank.saveToNBT(tag);
+ tList.appendTag(tag);
+ }
+ nbt.setTag("inventory", tList);
+ nbt.setInteger("tier", tier);
+ if (displayName != null) {
+ nbt.setString("displayName", displayName);
+ }
+ nbt.setBoolean("isUpgradeInventory", isUpgradeInventory);
+ return nbt;
+ }
+
+ /**
+ * Loads the Item Inventory Logic from an NBTTagList.
+ */
+ public void loadFromNBT(@Nonnull NBTTagCompound nbt) {
+ NBTTagList nbtList = nbt.getTagList("inventory", Constants.NBT.TAG_COMPOUND);
+ for (int i = 0; i < nbtList.tagCount(); i++) {
+ final NBTTagCompound tankNBT = nbtList.getCompoundTagAt(i);
+ final int tank = tankNBT.getShort("s");
+ if (tank >= 0 && tank < inventory.getTanks()) inventory.getFluidTank(tank)
+ .loadFromNBT(tankNBT);
+ if (inventory.getFluidInTank(tank) != null) {
+ fluidToTankMap.put(inventory.getFluidInTank(tank), inventory.getFluidTank(tank));
+ }
+ }
+ tier = nbt.getInteger("tier");
+ if (nbt.hasKey("displayName")) {
+ displayName = nbt.getString("displayName");
+ }
+ isUpgradeInventory = nbt.getBoolean("isUpgradeInventory");
+ }
+
+ @Nonnull
+ public IFluidTanksHandler getInventory() {
+ return inventory;
+ }
+
+ @Nonnull
+ public FluidStack[] getStoredFluids() {
+ final FluidStack[] fluids = inventory.getFluids()
+ .stream()
+ .filter(fluid -> fluid != null)
+ .collect(Collectors.toList())
+ .toArray(new FluidStack[0]);
+ if (fluids == null) {
+ return new FluidStack[0];
+ }
+ return fluids;
+ }
+
+ public boolean isFluidValid(@Nullable Fluid fluid) {
+ return fluid != null;
+ }
+
+ /**
+ * @param fluid What we are trying to input
+ * @param amount amount of fluid we are trying to put
+ * @return amount of fluid filled into the tank
+ */
+ public long fill(@Nullable Fluid fluid, long amount, boolean simulate) {
+ if (!isFluidValid(fluid)) return 0;
+ IFluidTankLong tank = fluidToTankMap.get(fluid);
+ if (tank != null) {
+ return tank.fill(fluid, amount, !simulate);
+ }
+ int tankNumber = 0;
+ tank = inventory.getFluidTank(tankNumber++);
+ while (tank.getStoredFluid() != fluid && tank.getStoredFluid() != null) {
+ tank = inventory.getFluidTank(tankNumber++);
+ }
+ fluidToTankMap.put(fluid, tank);
+ return tank.fill(fluid, amount, !simulate);
+ }
+
+ @Nullable
+ public FluidStack fill(@Nullable FluidStack fluid) {
+ if (fluid == null) return null;
+ for (int i = 0; i < inventory.getTanks(); i++) {
+ fill(fluid.getFluid(), fluid.amount, false);
+ }
+ return fluid;
+ }
+
+ /**
+ * Try and drain the first fluid found for that amount. Used by GT_Cover_Pump
+ *
+ * @param amount Fluid to drain from the tank
+ * @return A fluidstack with the possible amount drained
+ */
+ @Nullable
+ public FluidStack drain(long amount, boolean simulate) {
+ for (int i = 0; i < inventory.getTanks(); i++) {
+ Fluid fluid = inventory.getFluidInTank(i);
+ FluidStack drained = drain(fluid, amount, simulate);
+ if (drained != null) return drained;
+ }
+
+ return null;
+ }
+
+ @Nullable
+ public FluidStack drain(Fluid fluid, long amount, boolean simulate) {
+ if (!isFluidValid(fluid)) return null;
+ IFluidTankLong tank = fluidToTankMap.get(fluid);
+ if (tank != null) {
+ return tank.drain(amount, !simulate);
+ }
+ int tankNumber = 0;
+ tank = inventory.getFluidTank(tankNumber++);
+ while (tank.getStoredFluid() != fluid) {
+ tank = inventory.getFluidTank(tankNumber++);
+ }
+ fluidToTankMap.put(fluid, tank);
+ return tank.drain(amount, !simulate);
+ }
+
+ public void update() {
+ for (int i = 0; i < inventory.getTanks(); i++) {
+ IFluidTankLong tank = inventory.getFluidTank(i);
+ if (tank.getFluidAmountLong() > 0) continue;
+ tank.setFluid(null, 0);
+ }
+ }
+
+ public long calculateAmountOfTimesFluidCanBeTaken(Fluid fluid, long amountToTake) {
+ if (!isFluidValid(fluid)) return 0;
+ IFluidTankLong tank = fluidToTankMap.get(fluid);
+ if (tank == null) return 0;
+ return tank.getFluidAmountLong() / amountToTake;
+ }
+
+ @Nonnull
+ public Map<Fluid, Long> getMapOfStoredFluids() {
+ Map<Fluid, Long> map = new HashMap<>();
+ for (int i = 0; i < inventory.getTanks(); i++) {
+ IFluidTankLong tank = inventory.getFluidTank(i);
+ if (tank == null) continue;
+ Fluid fluid = tank.getStoredFluid();
+ if (fluid == null) continue;
+ map.put(fluid, map.getOrDefault(fluid, 0L) + tank.getFluidAmountLong());
+ }
+ return map;
+ }
+
+ /**
+ * Return a scrollable widget with only the inventory.
+ */
+ @Nonnull
+ public Widget getGuiPart() {
+ return getGUIPart(DEFAULT_COLUMNS_PER_ROW);
+ }
+
+ /**
+ * Return a scrollable widget with only the inventory.
+ */
+ @Nonnull
+ public Widget getGUIPart(int columnsPerRow) {
+ final Scrollable scrollable = new Scrollable();
+ scrollable.setVerticalScroll();
+ for (int rows = 0; rows * 4 < inventory.getTanks(); rows++) {
+ final int columnsToMake = Math.min(inventory.getTanks() - rows * 4, 4);
+ for (int column = 0; column < columnsToMake; column++) {
+ final FluidSlotWidget fluidSlot = new FluidSlotWidget(inventory, rows * 4 + column);
+ scrollable.widget(
+ fluidSlot.setPos(column * POSITION_INTERVAL, rows * POSITION_INTERVAL)
+ .setSize(SIZE));
+ }
+ }
+ return scrollable;
+ }
+}
diff --git a/src/main/java/gregtech/api/logic/ItemInventoryLogic.java b/src/main/java/gregtech/api/logic/ItemInventoryLogic.java
new file mode 100644
index 0000000000..69005216b8
--- /dev/null
+++ b/src/main/java/gregtech/api/logic/ItemInventoryLogic.java
@@ -0,0 +1,314 @@
+package gregtech.api.logic;
+
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.nbt.NBTTagList;
+import net.minecraftforge.common.util.Constants;
+
+import com.gtnewhorizons.modularui.api.forge.IItemHandlerModifiable;
+import com.gtnewhorizons.modularui.api.forge.ItemStackHandler;
+import com.gtnewhorizons.modularui.api.forge.ListItemHandler;
+import com.gtnewhorizons.modularui.api.math.Size;
+import com.gtnewhorizons.modularui.api.widget.Widget;
+import com.gtnewhorizons.modularui.common.widget.Scrollable;
+import com.gtnewhorizons.modularui.common.widget.SlotWidget;
+
+import gregtech.api.util.GT_Utility;
+import gregtech.api.util.item.ItemHolder;
+
+/**
+ * Generic Item logic for MuTEs.
+ *
+ * @author BlueWeabo
+ */
+public class ItemInventoryLogic {
+
+ private static final int DEFAULT_COLUMNS_PER_ROW = 4;
+ private static final int POSITION_INTERVAL = 18;
+ private static final Size SIZE = new Size(18, 18);
+
+ protected String displayName;
+ @Nonnull
+ protected final IItemHandlerModifiable inventory;
+ protected UUID connectedFluidInventory;
+ protected int tier;
+ protected boolean isUpgradeInventory;
+ protected Map<ItemHolder, Long> cachedItemMap;
+ protected boolean inRecipeCheck;
+
+ public ItemInventoryLogic(int numberOfSlots) {
+ this(numberOfSlots, 0);
+ }
+
+ public ItemInventoryLogic(int numberOfSlots, int tier) {
+ this(new ItemStackHandler(numberOfSlots), tier, false);
+ }
+
+ public ItemInventoryLogic(int numberOfSlots, int tier, boolean isUpgradeInventory) {
+ this(new ItemStackHandler(numberOfSlots), tier, isUpgradeInventory);
+ }
+
+ public ItemInventoryLogic(@Nonnull IItemHandlerModifiable inventory, int tier, boolean isUpgradeInventory) {
+ this.inventory = inventory;
+ this.tier = tier;
+ this.isUpgradeInventory = isUpgradeInventory;
+ }
+
+ public ItemInventoryLogic(Collection<IItemHandlerModifiable> inventories) {
+ this(new ListItemHandler(inventories), -1, false);
+ }
+
+ @Nullable
+ public String getDisplayName() {
+ return displayName;
+ }
+
+ public int getTier() {
+ return tier;
+ }
+
+ public boolean isUpgradeInventory() {
+ return isUpgradeInventory;
+ }
+
+ public int getSlots() {
+ return getInventory().getSlots();
+ }
+
+ public void setDisplayName(@Nullable String displayName) {
+ this.displayName = displayName;
+ }
+
+ @Nullable
+ public UUID getConnectedFluidInventoryID() {
+ return connectedFluidInventory;
+ }
+
+ public void setConnectedFluidInventoryID(@Nullable UUID connectedFluidTank) {
+ this.connectedFluidInventory = connectedFluidTank;
+ }
+
+ /**
+ *
+ * @return The Item Inventory Logic as an NBTTagCompound to be saved in another nbt as how one wants.
+ */
+ @Nonnull
+ public NBTTagCompound saveToNBT() {
+ final NBTTagCompound nbt = new NBTTagCompound();
+ final NBTTagList tList = new NBTTagList();
+ for (int slot = 0; slot < inventory.getSlots(); slot++) {
+ final ItemStack tStack = inventory.getStackInSlot(slot);
+ if (tStack == null) continue;
+
+ final NBTTagCompound tag = new NBTTagCompound();
+ tag.setByte("s", (byte) slot);
+ tStack.writeToNBT(tag);
+ tList.appendTag(tag);
+ }
+ nbt.setTag("inventory", tList);
+ nbt.setInteger("tier", tier);
+ if (displayName != null) {
+ nbt.setString("displayName", displayName);
+ }
+ nbt.setBoolean("isUpgradeInventory", isUpgradeInventory);
+ if (connectedFluidInventory != null) {
+ nbt.setString("connectedFluidInventory", connectedFluidInventory.toString());
+ }
+ return nbt;
+ }
+
+ /**
+ * Loads the Item Inventory Logic from an NBTTagCompound.
+ */
+ public void loadFromNBT(@Nonnull NBTTagCompound nbt) {
+ tier = nbt.getInteger("tier");
+ if (nbt.hasKey("displayName")) {
+ displayName = nbt.getString("displayName");
+ }
+
+ isUpgradeInventory = nbt.getBoolean("isUpgradeInventory");
+ if (nbt.hasKey("connectedFluidInventory")) {
+ connectedFluidInventory = UUID.fromString(nbt.getString("connectedFluidInventory"));
+ }
+
+ NBTTagList nbtList = nbt.getTagList("inventory", Constants.NBT.TAG_COMPOUND);
+ if (nbtList == null) return;
+
+ for (int i = 0; i < nbtList.tagCount(); i++) {
+ final NBTTagCompound tNBT = nbtList.getCompoundTagAt(i);
+ final int tSlot = tNBT.getShort("s");
+ if (tSlot >= 0 && tSlot < inventory.getSlots()) {
+ inventory.setStackInSlot(tSlot, GT_Utility.loadItem(tNBT));
+ }
+ }
+ }
+
+ @Nonnull
+ public IItemHandlerModifiable getInventory() {
+ return inventory;
+ }
+
+ @Nonnull
+ public ItemStack[] getStoredItems() {
+ final ItemStack[] items = inventory.getStacks()
+ .stream()
+ .filter(item -> item != null)
+ .collect(Collectors.toList())
+ .toArray(new ItemStack[0]);
+ if (items == null) {
+ return new ItemStack[0];
+ }
+ return items;
+ }
+
+ public boolean isStackValid(ItemStack item) {
+ return true;
+ }
+
+ @Nullable
+ public ItemStack insertItem(ItemStack item) {
+ if (!isStackValid(item)) return item;
+ for (int i = 0; i < inventory.getSlots() && item != null && item.stackSize > 0; i++) {
+ item = inventory.insertItem(i, item, false);
+ }
+ return item;
+ }
+
+ @Nullable
+ public ItemStack extractItem(int slot, int amount) {
+ return inventory.extractItem(slot, amount, false);
+ }
+
+ public boolean subtractItemAmount(@Nonnull ItemHolder item, long amount, boolean simulate) {
+ Map<ItemHolder, Long> itemMap = getMapOfStoredItems();
+ if (!itemMap.containsKey(item)) {
+ return false;
+ }
+
+ if (itemMap.get(item) < amount) {
+ return false;
+ }
+
+ if (simulate) {
+ return true;
+ }
+
+ itemMap.put(item, itemMap.get(item) - amount);
+ return true;
+ }
+
+ @Nullable
+ public ItemStack getItemInSlot(int slot) {
+ return inventory.getStackInSlot(slot);
+ }
+
+ public void sort() {
+ Map<ItemHolder, Long> itemMap = getMapOfStoredItems();
+ List<ItemHolder> sortedItems = itemMap.keySet()
+ .stream()
+ .sorted(
+ Comparator.comparing(
+ a -> a.getItem()
+ .getUnlocalizedName() + a.getMeta()))
+ .collect(Collectors.toList());
+ putInItemsFromMap(itemMap, sortedItems);
+ }
+
+ public void update(boolean shouldSort) {
+ if (shouldSort) {
+ sort();
+ }
+
+ for (int i = 0; i < inventory.getSlots(); i++) {
+ ItemStack item = inventory.getStackInSlot(i);
+ if (item == null) continue;
+ if (item.stackSize > 0) continue;
+ inventory.setStackInSlot(i, null);
+ }
+ }
+
+ /**
+ * Return a scrollable widget with only the inventory.
+ */
+ @Nonnull
+ public Widget getGuiPart() {
+ return getGUIPart(DEFAULT_COLUMNS_PER_ROW);
+ }
+
+ /**
+ * Return a scrollable widget with only the inventory.
+ */
+ @Nonnull
+ public Widget getGUIPart(int columnsPerRow) {
+ final Scrollable scrollable = new Scrollable();
+ scrollable.setVerticalScroll();
+ for (int rows = 0; rows * columnsPerRow < Math.min(inventory.getSlots(), 128); rows++) {
+ final int columnsToMake = Math
+ .min(Math.min(inventory.getSlots(), 128) - rows * columnsPerRow, columnsPerRow);
+ for (int column = 0; column < columnsToMake; column++) {
+ scrollable.widget(
+ new SlotWidget(inventory, rows * columnsPerRow + column)
+ .setPos(column * POSITION_INTERVAL, rows * POSITION_INTERVAL)
+ .setSize(SIZE));
+ }
+ }
+ return scrollable;
+ }
+
+ public void startRecipeCheck() {
+ cachedItemMap = getMapOfStoredItems();
+ inRecipeCheck = true;
+ }
+
+ public void stopRecipeCheck() {
+ inRecipeCheck = false;
+ putInItemsFromMap(cachedItemMap, null);
+ cachedItemMap = null;
+ }
+
+ @Nonnull
+ public Map<ItemHolder, Long> getMapOfStoredItems() {
+ if (inRecipeCheck) return cachedItemMap;
+ Map<ItemHolder, Long> items = new HashMap<>();
+ for (int i = 0; i < inventory.getSlots(); i++) {
+ ItemStack item = extractItem(i, Integer.MAX_VALUE);
+ if (item == null) continue;
+ ItemHolder itemHolder = new ItemHolder(item);
+ items.put(itemHolder, items.getOrDefault(itemHolder, 0L) + item.stackSize);
+ }
+ return items;
+ }
+
+ protected void putInItemsFromMap(@Nonnull Map<ItemHolder, Long> itemMap, @Nullable List<ItemHolder> sortedList) {
+ for (ItemHolder itemHolder : (sortedList == null ? itemMap.keySet() : sortedList)) {
+ long itemAmount = itemMap.get(itemHolder);
+ ItemStack item = new ItemStack(itemHolder.getItem(), 0, itemHolder.getMeta());
+ item.setTagCompound(itemHolder.getNBT());
+ while (itemAmount > 0) {
+ item.stackSize = (int) Math.min(item.getMaxStackSize(), itemAmount);
+ itemAmount -= item.stackSize;
+ insertItem(item);
+ }
+ }
+ }
+
+ public long calculateAmountOfTimesItemCanBeTaken(ItemHolder item, long amount) {
+ return getMapOfStoredItems().getOrDefault(item, 0L) / amount;
+ }
+
+ public Set<ItemHolder> getSetOfStoredItems() {
+ return getMapOfStoredItems().keySet();
+ }
+}
diff --git a/src/main/java/gregtech/api/logic/ModelRenderLogic.java b/src/main/java/gregtech/api/logic/ModelRenderLogic.java
new file mode 100644
index 0000000000..d9f2fdcf27
--- /dev/null
+++ b/src/main/java/gregtech/api/logic/ModelRenderLogic.java
@@ -0,0 +1,5 @@
+package gregtech.api.logic;
+
+public abstract class ModelRenderLogic {
+
+}
diff --git a/src/main/java/gregtech/api/logic/MuTEProcessingLogic.java b/src/main/java/gregtech/api/logic/MuTEProcessingLogic.java
new file mode 100644
index 0000000000..da53c8875d
--- /dev/null
+++ b/src/main/java/gregtech/api/logic/MuTEProcessingLogic.java
@@ -0,0 +1,256 @@
+package gregtech.api.logic;
+
+import static net.minecraftforge.common.util.Constants.NBT.TAG_COMPOUND;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.UUID;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.nbt.NBTTagList;
+import net.minecraftforge.fluids.FluidStack;
+
+import com.gtnewhorizons.modularui.api.screen.ModularWindow;
+import com.gtnewhorizons.modularui.api.widget.Widget;
+import com.gtnewhorizons.modularui.common.widget.Scrollable;
+
+import gregtech.api.enums.InventoryType;
+import gregtech.api.logic.interfaces.ProcessingLogicHost;
+import gregtech.api.recipe.RecipeMap;
+import gregtech.api.recipe.check.CheckRecipeResult;
+import gregtech.api.recipe.check.CheckRecipeResultRegistry;
+import gregtech.api.util.GT_OverclockCalculator;
+import gregtech.api.util.GT_ParallelHelper;
+import gregtech.api.util.GT_Recipe;
+import gregtech.api.util.GT_Utility;
+
+/**
+ * Processing logic class, dedicated for MultiTileEntities.
+ */
+public class MuTEProcessingLogic<P extends MuTEProcessingLogic<P>> extends AbstractProcessingLogic<P> {
+
+ protected boolean hasWork;
+ protected int progress;
+ protected ProcessingLogicHost<P> machineHost;
+ @Nonnull
+ protected CheckRecipeResult recipeResult = CheckRecipeResultRegistry.NONE;
+ @Nullable
+ protected UUID itemOutputID;
+ @Nullable
+ protected UUID fluidOutputID;
+
+ public P setMachineHost(@Nonnull ProcessingLogicHost<P> machineHost) {
+ this.machineHost = machineHost;
+ return getThis();
+ }
+
+ // #region Logic
+
+ @Nonnull
+ @Override
+ public CheckRecipeResult process() {
+ RecipeMap<?> recipeMap = preProcess();
+
+ ItemInventoryLogic itemInput = null;
+ FluidInventoryLogic fluidInput = null;
+ if (machineHost.isInputSeparated()) {
+ for (Map.Entry<UUID, ItemInventoryLogic> itemEntry : machineHost
+ .getAllItemInventoryLogics(InventoryType.Input)) {
+ itemOutputID = Objects.requireNonNull(itemEntry.getKey());
+ itemInput = Objects.requireNonNull(itemEntry.getValue());
+ fluidInput = Objects.requireNonNull(
+ machineHost.getFluidLogic(InventoryType.Input, itemInput.getConnectedFluidInventoryID()));
+ fluidOutputID = itemInput.getConnectedFluidInventoryID();
+ }
+ } else {
+ itemInput = Objects.requireNonNull(machineHost.getItemLogic(InventoryType.Input, null));
+ fluidInput = Objects.requireNonNull(machineHost.getFluidLogic(InventoryType.Input, null));
+ }
+
+ CheckRecipeResult recipeValidatorResult = null;
+ if (recipeValidatorResult != null) {
+ return recipeValidatorResult;
+ }
+
+ return processRecipe(null, Objects.requireNonNull(itemInput), Objects.requireNonNull(fluidInput));
+ }
+
+ @Nonnull
+ protected CheckRecipeResult processRecipe(@Nonnull List<GT_Recipe> recipes, @Nonnull ItemInventoryLogic itemInput,
+ @Nonnull FluidInventoryLogic fluidInput) {
+ CheckRecipeResult result = CheckRecipeResultRegistry.INTERNAL_ERROR;
+ for (GT_Recipe recipe : recipes) {
+ Objects.requireNonNull(recipe);
+ GT_ParallelHelper helper = createParallelHelper(recipe, itemInput, fluidInput);
+ GT_OverclockCalculator calculator = createOverclockCalculator(recipe);
+ helper.setCalculator(calculator);
+ helper.build();
+ result = helper.getResult();
+ if (result.wasSuccessful()) {
+ return applyRecipe(recipe, helper, calculator, result);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Override if you don't work with regular gt recipe maps
+ */
+ @Nonnull
+ protected Object findRecipe(@Nullable RecipeMap<?> map, @Nonnull ItemInventoryLogic itemInput,
+ @Nonnull FluidInventoryLogic fluidInput) {
+ if (map == null) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Nonnull
+ protected GT_ParallelHelper createParallelHelper(@Nonnull GT_Recipe recipe, @Nonnull ItemInventoryLogic itemInput,
+ @Nonnull FluidInventoryLogic fluidInput) {
+ return new GT_ParallelHelper().setRecipe(recipe)
+ .setItemInputInventory(itemInput)
+ .setFluidInputInventory(fluidInput)
+ .setAvailableEUt(availableVoltage * availableAmperage)
+ .setMaxParallel(maxParallel)
+ .setEUtModifier(euModifier)
+ .enableBatchMode(batchSize)
+ .setConsumption(true)
+ .setOutputCalculation(true)
+ .setMuTEMode(true);
+ }
+
+ // #endregion
+
+ // #region Getters
+
+ @Nonnull
+ public CheckRecipeResult getResult() {
+ return recipeResult;
+ }
+
+ public int getProgress() {
+ return progress;
+ }
+
+ // #endregion
+
+ // #region Other
+
+ public void startCheck() {
+ recipeResult = process();
+ }
+
+ public void progress() {
+ if (!hasWork) return;
+ if (progress == duration) {
+ progress = 0;
+ duration = 0;
+ calculatedEut = 0;
+ output();
+ return;
+ }
+ progress++;
+ }
+
+ protected void output() {
+ ItemInventoryLogic itemOutput = machineHost.getItemLogic(InventoryType.Output, itemOutputID);
+ FluidInventoryLogic fluidOutput = machineHost.getFluidLogic(InventoryType.Output, fluidOutputID);
+ if (itemOutput == null || fluidOutput == null) return;
+ for (ItemStack item : outputItems) {
+ if (item == null) continue;
+ itemOutput.insertItem(item);
+ }
+ for (FluidStack fluid : outputFluids) {
+ if (fluid == null) continue;
+ fluidOutput.fill(fluid.getFluid(), fluid.amount, false);
+ }
+ outputItems = new ItemStack[0];
+ outputFluids = new FluidStack[0];
+ }
+
+ public boolean canWork() {
+ return !hasWork && machineHost.isAllowedToWork();
+ }
+
+ /**
+ * By how much to increase the progress?
+ *
+ * @param progressAmount in ticks
+ */
+ public void increaseProgress(int progressAmount) {
+ progress += progressAmount;
+ }
+
+ public NBTTagCompound saveToNBT() {
+ NBTTagCompound logicNBT = new NBTTagCompound();
+ logicNBT.setLong("eutConsumption", calculatedEut);
+ logicNBT.setInteger("duration", duration);
+ logicNBT.setInteger("progress", progress);
+ logicNBT.setBoolean("hasWork", hasWork);
+ if (outputItems != null) {
+ NBTTagList itemOutputsNBT = new NBTTagList();
+ for (ItemStack item : outputItems) {
+ itemOutputsNBT.appendTag(GT_Utility.saveItem(item));
+ }
+ logicNBT.setTag("itemOutputs", itemOutputsNBT);
+ }
+ if (outputFluids != null) {
+ NBTTagList fluidOutputsNBT = new NBTTagList();
+ for (FluidStack fluid : outputFluids) {
+ fluidOutputsNBT.appendTag(fluid.writeToNBT(new NBTTagCompound()));
+ }
+ logicNBT.setTag("fluidOutputs", fluidOutputsNBT);
+ }
+ if (itemOutputID != null) {
+ logicNBT.setString("itemOutputID", itemOutputID.toString());
+ }
+ if (fluidOutputID != null) {
+ logicNBT.setString("fluidOutputID", fluidOutputID.toString());
+ }
+ return logicNBT;
+ }
+
+ public void loadFromNBT(@Nonnull NBTTagCompound logicNBT) {
+ calculatedEut = logicNBT.getLong("eutConsumption");
+ duration = logicNBT.getInteger("duration");
+ progress = logicNBT.getInteger("progress");
+ hasWork = logicNBT.getBoolean("hasWork");
+ if (logicNBT.hasKey("itemOutputs")) {
+ NBTTagList itemOutputsNBT = logicNBT.getTagList("itemOutputs", TAG_COMPOUND);
+ outputItems = new ItemStack[itemOutputsNBT.tagCount()];
+ for (int i = 0; i < itemOutputsNBT.tagCount(); i++) {
+ outputItems[i] = GT_Utility.loadItem(itemOutputsNBT.getCompoundTagAt(i));
+ }
+ }
+ if (logicNBT.hasKey("fluidOutputs")) {
+ NBTTagList fluidOutputsNBT = logicNBT.getTagList("fluidOutputs", TAG_COMPOUND);
+ outputFluids = new FluidStack[fluidOutputsNBT.tagCount()];
+ for (int i = 0; i < fluidOutputsNBT.tagCount(); i++) {
+ outputFluids[i] = FluidStack.loadFluidStackFromNBT(fluidOutputsNBT.getCompoundTagAt(i));
+ }
+ }
+ if (logicNBT.hasKey("itemOutputID")) {
+ itemOutputID = UUID.fromString(logicNBT.getString("itemOutputID"));
+ }
+ if (logicNBT.hasKey("fluidOutputID")) {
+ fluidOutputID = UUID.fromString(logicNBT.getString("fluidOutputID"));
+ }
+ }
+
+ /**
+ * Returns a gui part, which will be displayed in a separate tab on the machine's gui.
+ */
+ @Nonnull
+ public Widget getGUIPart(ModularWindow.Builder builder) {
+ return new Scrollable();
+ }
+
+ // #endregion
+}
diff --git a/src/main/java/gregtech/api/logic/NullPowerLogic.java b/src/main/java/gregtech/api/logic/NullPowerLogic.java
new file mode 100644
index 0000000000..0017f0e647
--- /dev/null
+++ b/src/main/java/gregtech/api/logic/NullPowerLogic.java
@@ -0,0 +1,5 @@
+package gregtech.api.logic;
+
+public class NullPowerLogic extends PowerLogic {
+
+}
diff --git a/src/main/java/gregtech/api/logic/PowerLogic.java b/src/main/java/gregtech/api/logic/PowerLogic.java
new file mode 100644
index 0000000000..ad19987a76
--- /dev/null
+++ b/src/main/java/gregtech/api/logic/PowerLogic.java
@@ -0,0 +1,254 @@
+package gregtech.api.logic;
+
+import static gregtech.common.misc.WirelessNetworkManager.addEUToGlobalEnergyMap;
+
+import java.util.UUID;
+
+import javax.annotation.Nonnull;
+
+import net.minecraft.nbt.NBTTagCompound;
+
+import gregtech.api.enums.GT_Values.NBT;
+
+/**
+ * Power logic for machines. This is used to store all the important variables for a machine to have energy and use it
+ * in any way.
+ *
+ * @author BlueWeabo, Maxim
+ */
+public class PowerLogic {
+
+ public static final int NONE = 0;
+ public static final int RECEIVER = 1;
+ public static final int EMITTER = 2;
+ public static final int BOTH = RECEIVER | EMITTER;
+ private static float wirelessChargeFactor = 0.5F;
+ private long storedEnergy = 0;
+ private long energyCapacity = 0;
+ private long voltage = 0;
+ private long amperage = 0;
+ private int type = 0;
+ private boolean canUseLaser = false;
+ private boolean canUseWireless = false;
+ private UUID owner;
+
+ public PowerLogic() {}
+
+ /**
+ * Sets the max voltage the logic can accept
+ */
+ @Nonnull
+ public PowerLogic setMaxVoltage(long voltage) {
+ this.voltage = voltage;
+ return this;
+ }
+
+ /**
+ * Sets the maximum amount of energy the machine can store inside of it
+ */
+ @Nonnull
+ public PowerLogic setEnergyCapacity(long energyCapacity) {
+ this.energyCapacity = energyCapacity;
+ return this;
+ }
+
+ /**
+ * Sets the maximum amount of amps a machine can receive from an emitter
+ */
+ @Nonnull
+ public PowerLogic setMaxAmperage(long amperage) {
+ this.amperage = amperage;
+ return this;
+ }
+
+ /**
+ * Sets the type of power logic this is. Whether it will receive EU or emit it to others, or do both
+ */
+ @Nonnull
+ public PowerLogic setType(int type) {
+ this.type = type;
+ return this;
+ }
+
+ /**
+ * If this power logic can use lasers to be used for it
+ */
+ @Nonnull
+ public PowerLogic setCanUseLaser(boolean canUse) {
+ canUseLaser = canUse;
+ return this;
+ }
+
+ /**
+ * If the power logic should use wireless EU first before using its internal buffer
+ */
+ @Nonnull
+ public PowerLogic setCanUseWireless(boolean canUse, UUID owner) {
+ canUseWireless = canUse;
+ this.owner = owner;
+ return this;
+ }
+
+ /**
+ * Adding energy directly to the buffer, but only if it has the capacity.
+ */
+ public boolean addEnergyUnsafe(long totalEUAdded) {
+ if (storedEnergy + totalEUAdded >= energyCapacity) {
+ return false;
+ }
+
+ storedEnergy += totalEUAdded;
+ return true;
+ }
+
+ /**
+ * Adding energy to the buffer if the voltage given isn't higher than the voltage of the logic
+ */
+ public boolean addEnergy(long voltage, long amperage) {
+ if (voltage > this.voltage) {
+ return false;
+ }
+
+ return addEnergyUnsafe(voltage * amperage);
+ }
+
+ /**
+ * Same as {@link #addEnergy(long, long)}, but only 1 amp of it
+ */
+ public boolean addEnergy(long voltage) {
+ return addEnergy(voltage, 1);
+ }
+
+ /**
+ * Injecting energy in the multiblock ampere per ampere until full or until we have added the maximum possible
+ * amperes for this tick
+ *
+ * @param voltage At what voltage are the amps?
+ * @param availableAmperage How much amperage do we have available
+ * @return Amount of amperes used
+ */
+ public long injectEnergy(long voltage, long availableAmperage) {
+ if (canUseWireless) return 0;
+ long usedAmperes = 0;
+ while (addEnergy(voltage, 1) && usedAmperes < amperage) {
+ usedAmperes++;
+ }
+
+ return usedAmperes;
+ }
+
+ /**
+ * Remove energy from the logic only if it has enough to be removed.
+ */
+ public boolean removeEnergyUnsafe(long totalEURemoved) {
+ if (canUseWireless) {
+ if (storedEnergy < energyCapacity * wirelessChargeFactor) {
+ if (addEUToGlobalEnergyMap(owner, -(energyCapacity - storedEnergy))) {
+ storedEnergy = energyCapacity;
+ }
+ }
+ }
+ if (storedEnergy - totalEURemoved < 0) {
+ return false;
+ }
+
+ storedEnergy -= totalEURemoved;
+ return true;
+ }
+
+ /**
+ * Remove the given voltage for the amount of amperage if the removed isn't higher than the logic's voltage
+ */
+ public boolean removeEnergy(long voltage, long amperage) {
+ if (voltage > this.voltage) {
+ return false;
+ }
+
+ return removeEnergyUnsafe(voltage * amperage);
+ }
+
+ /**
+ * Same as {@link #removeEnergy(long, long)}, but with only 1 amperage
+ */
+ public boolean removeEnergy(long voltage) {
+ return removeEnergy(voltage, 1);
+ }
+
+ /**
+ * @return The maximum energy that can be stored.
+ */
+ public long getCapacity() {
+ return energyCapacity;
+ }
+
+ /**
+ * @return The maximum voltage that is available
+ */
+ public long getVoltage() {
+ return voltage;
+ }
+
+ /**
+ * @return The current energy stored
+ */
+ public long getStoredEnergy() {
+ return storedEnergy;
+ }
+
+ /**
+ * @return The current maximum Amperage
+ */
+ public long getMaxAmperage() {
+ return amperage;
+ }
+
+ /**
+ * Is the logic a receiver to receive energy
+ */
+ public boolean isEnergyReceiver() {
+ return (type & RECEIVER) > 0;
+ }
+
+ /**
+ * Is the logic a emitter to emit energy
+ */
+ public boolean isEnergyEmitter() {
+ return (type & EMITTER) > 0;
+ }
+
+ /**
+ * Saves the power logic to its own nbt tag before saving it to the given one.
+ *
+ * @param nbt Tag where you want to save the power logic tag to.
+ */
+ public void saveToNBT(NBTTagCompound nbt) {
+ NBTTagCompound powerLogic = new NBTTagCompound();
+ powerLogic.setLong(NBT.POWER_LOGIC_ENERGY_CAPACITY, energyCapacity);
+ powerLogic.setLong(NBT.POWER_LOGIC_STORED_ENERGY, storedEnergy);
+ powerLogic.setLong(NBT.POWER_LOGIC_AMPERAGE, amperage);
+ powerLogic.setLong(NBT.POWER_LOGIC_VOLTAGE, voltage);
+ powerLogic.setInteger(NBT.POWER_LOGIC_TYPE, type);
+ nbt.setTag(NBT.POWER_LOGIC, powerLogic);
+ }
+
+ /**
+ * Loads the power logic from its own nbt after getting it from the given one
+ *
+ * @param nbt Tag where the power logic tag was saved to
+ */
+ public void loadFromNBT(NBTTagCompound nbt) {
+ NBTTagCompound powerLogic = nbt.getCompoundTag(NBT.POWER_LOGIC);
+ energyCapacity = powerLogic.getLong(NBT.POWER_LOGIC_ENERGY_CAPACITY);
+ storedEnergy = powerLogic.getLong(NBT.POWER_LOGIC_STORED_ENERGY);
+ amperage = powerLogic.getLong(NBT.POWER_LOGIC_AMPERAGE);
+ voltage = powerLogic.getLong(NBT.POWER_LOGIC_VOLTAGE);
+ type = powerLogic.getInteger(NBT.POWER_LOGIC_TYPE);
+ }
+
+ /**
+ * Can we use lasers for inputting EU
+ */
+ public boolean canUseLaser() {
+ return canUseLaser;
+ }
+}
diff --git a/src/main/java/gregtech/api/logic/ProcessingLogic.java b/src/main/java/gregtech/api/logic/ProcessingLogic.java
new file mode 100644
index 0000000000..4d203ed80f
--- /dev/null
+++ b/src/main/java/gregtech/api/logic/ProcessingLogic.java
@@ -0,0 +1,228 @@
+package gregtech.api.logic;
+
+import java.util.List;
+import java.util.stream.Stream;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.minecraft.item.ItemStack;
+import net.minecraftforge.fluids.FluidStack;
+
+import gregtech.api.interfaces.tileentity.IRecipeLockable;
+import gregtech.api.recipe.RecipeMap;
+import gregtech.api.recipe.check.CheckRecipeResult;
+import gregtech.api.recipe.check.CheckRecipeResultRegistry;
+import gregtech.api.recipe.check.SingleRecipeCheck;
+import gregtech.api.util.GT_OverclockCalculator;
+import gregtech.api.util.GT_ParallelHelper;
+import gregtech.api.util.GT_Recipe;
+
+/**
+ * Logic class to calculate result of recipe check from inputs, based on recipemap.
+ */
+@SuppressWarnings({ "unused", "UnusedReturnValue" })
+public class ProcessingLogic extends AbstractProcessingLogic<ProcessingLogic> {
+
+ protected IRecipeLockable recipeLockableMachine;
+ protected ItemStack specialSlotItem;
+ protected ItemStack[] inputItems;
+ protected FluidStack[] inputFluids;
+ protected boolean isRecipeLocked;
+
+ public ProcessingLogic() {}
+
+ // #region Setters
+
+ @Nonnull
+ public ProcessingLogic setInputItems(ItemStack... itemInputs) {
+ this.inputItems = itemInputs;
+ return getThis();
+ }
+
+ @Nonnull
+ public ProcessingLogic setInputItems(List<ItemStack> itemOutputs) {
+ this.inputItems = itemOutputs.toArray(new ItemStack[0]);
+ return getThis();
+ }
+
+ @Nonnull
+ public ProcessingLogic setInputFluids(FluidStack... fluidInputs) {
+ this.inputFluids = fluidInputs;
+ return getThis();
+ }
+
+ @Nonnull
+ public ProcessingLogic setInputFluids(List<FluidStack> fluidInputs) {
+ this.inputFluids = fluidInputs.toArray(new FluidStack[0]);
+ return getThis();
+ }
+
+ public ProcessingLogic setSpecialSlotItem(ItemStack specialSlotItem) {
+ this.specialSlotItem = specialSlotItem;
+ return getThis();
+ }
+
+ /**
+ * Enables single recipe locking mode.
+ */
+ public ProcessingLogic setRecipeLocking(IRecipeLockable recipeLockableMachine, boolean isRecipeLocked) {
+ this.recipeLockableMachine = recipeLockableMachine;
+ this.isRecipeLocked = isRecipeLocked;
+ return getThis();
+ }
+
+ /**
+ * Clears calculated results and provided machine inputs to prepare for the next machine operation.
+ */
+
+ public ProcessingLogic clear() {
+ this.inputItems = null;
+ this.inputFluids = null;
+ this.specialSlotItem = null;
+ this.outputItems = null;
+ this.outputFluids = null;
+ this.calculatedEut = 0;
+ this.duration = 0;
+ this.calculatedParallels = 0;
+ return getThis();
+ }
+
+ // #endregion
+
+ // #region Logic
+
+ /**
+ * Executes the recipe check: Find recipe from recipemap, Calculate parallel, overclock and outputs.
+ */
+ @Nonnull
+ public CheckRecipeResult process() {
+ RecipeMap<?> recipeMap = preProcess();
+
+ if (inputItems == null) {
+ inputItems = new ItemStack[0];
+ }
+ if (inputFluids == null) {
+ inputFluids = new FluidStack[0];
+ }
+
+ if (isRecipeLocked && recipeLockableMachine != null && recipeLockableMachine.getSingleRecipeCheck() != null) {
+ // Recipe checker is already built, we'll use it
+ SingleRecipeCheck singleRecipeCheck = recipeLockableMachine.getSingleRecipeCheck();
+ // Validate recipe here, otherwise machine will show "not enough output space"
+ // even if recipe cannot be found
+ if (singleRecipeCheck.checkRecipeInputs(false, 1, inputItems, inputFluids) == 0) {
+ return CheckRecipeResultRegistry.NO_RECIPE;
+ }
+
+ return validateAndCalculateRecipe(
+ recipeLockableMachine.getSingleRecipeCheck()
+ .getRecipe()).checkRecipeResult;
+ }
+ Stream<GT_Recipe> matchedRecipes = findRecipeMatches(recipeMap);
+ Iterable<GT_Recipe> recipeIterable = matchedRecipes::iterator;
+ CheckRecipeResult checkRecipeResult = CheckRecipeResultRegistry.NO_RECIPE;
+ for (GT_Recipe matchedRecipe : recipeIterable) {
+ CalculationResult foundResult = validateAndCalculateRecipe(matchedRecipe);
+ if (foundResult.successfullyConsumedInputs) {
+ // Successfully found and set recipe, so return it
+ return foundResult.checkRecipeResult;
+ }
+ if (foundResult.checkRecipeResult != CheckRecipeResultRegistry.NO_RECIPE) {
+ // Recipe failed in interesting way, so remember that and continue searching
+ checkRecipeResult = foundResult.checkRecipeResult;
+ }
+ }
+ return checkRecipeResult;
+ }
+
+ /**
+ * Checks if supplied recipe is valid for process. This involves voltage check, output full check. If successful,
+ * additionally performs input consumption, output calculation with parallel, and overclock calculation.
+ *
+ * @param recipe The recipe which will be checked and processed
+ */
+ @Nonnull
+ private CalculationResult validateAndCalculateRecipe(@Nonnull GT_Recipe recipe) {
+ CheckRecipeResult result = validateRecipe(recipe);
+ if (!result.wasSuccessful()) {
+ return CalculationResult.ofFailure(result);
+ }
+
+ GT_ParallelHelper helper = createParallelHelper(recipe);
+ GT_OverclockCalculator calculator = createOverclockCalculator(recipe);
+ helper.setCalculator(calculator);
+ helper.build();
+
+ if (!helper.getResult()
+ .wasSuccessful()) {
+ return CalculationResult.ofFailure(helper.getResult());
+ }
+
+ return CalculationResult.ofSuccess(applyRecipe(recipe, helper, calculator, result));
+ }
+
+ /**
+ * Finds a list of matched recipes. At this point no additional check to the matched recipe has been done.
+ * <p>
+ * Override {@link #validateRecipe} to have custom check.
+ * <p>
+ * Override this method if it doesn't work with normal recipemaps.
+ */
+ @Nonnull
+ protected Stream<GT_Recipe> findRecipeMatches(@Nullable RecipeMap<?> map) {
+ if (map == null) {
+ return Stream.empty();
+ }
+ return map.findRecipeQuery()
+ .items(inputItems)
+ .fluids(inputFluids)
+ .specialSlot(specialSlotItem)
+ .cachedRecipe(lastRecipe)
+ .findAll();
+ }
+
+ /**
+ * Override to tweak parallel logic if needed.
+ */
+ @Nonnull
+ protected GT_ParallelHelper createParallelHelper(@Nonnull GT_Recipe recipe) {
+ return new GT_ParallelHelper().setRecipe(recipe)
+ .setItemInputs(inputItems)
+ .setFluidInputs(inputFluids)
+ .setAvailableEUt(availableVoltage * availableAmperage)
+ .setMachine(machine, protectItems, protectFluids)
+ .setRecipeLocked(recipeLockableMachine, isRecipeLocked)
+ .setMaxParallel(maxParallel)
+ .setEUtModifier(euModifier)
+ .enableBatchMode(batchSize)
+ .setConsumption(true)
+ .setOutputCalculation(true);
+ }
+
+ // #endregion
+
+ /**
+ * Represents the status of check recipe calculation. {@link #successfullyConsumedInputs} does not necessarily mean
+ * {@link #checkRecipeResult} being successful, when duration or power is overflowed. Being failure means
+ * recipe cannot meet requirements and recipe search should be continued if possible.
+ */
+ protected final static class CalculationResult {
+
+ public final boolean successfullyConsumedInputs;
+ public final CheckRecipeResult checkRecipeResult;
+
+ public static CalculationResult ofSuccess(CheckRecipeResult checkRecipeResult) {
+ return new CalculationResult(true, checkRecipeResult);
+ }
+
+ public static CalculationResult ofFailure(CheckRecipeResult checkRecipeResult) {
+ return new CalculationResult(false, checkRecipeResult);
+ }
+
+ private CalculationResult(boolean successfullyConsumedInputs, CheckRecipeResult checkRecipeResult) {
+ this.successfullyConsumedInputs = successfullyConsumedInputs;
+ this.checkRecipeResult = checkRecipeResult;
+ }
+ }
+}
diff --git a/src/main/java/gregtech/api/logic/interfaces/FluidInventoryLogicHost.java b/src/main/java/gregtech/api/logic/interfaces/FluidInventoryLogicHost.java
new file mode 100644
index 0000000000..c12333a4c6
--- /dev/null
+++ b/src/main/java/gregtech/api/logic/interfaces/FluidInventoryLogicHost.java
@@ -0,0 +1,95 @@
+package gregtech.api.logic.interfaces;
+
+import static com.google.common.primitives.Ints.saturatedCast;
+
+import java.util.HashSet;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Set;
+import java.util.UUID;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.minecraftforge.common.util.ForgeDirection;
+import net.minecraftforge.fluids.Fluid;
+import net.minecraftforge.fluids.FluidStack;
+import net.minecraftforge.fluids.FluidTankInfo;
+import net.minecraftforge.fluids.IFluidHandler;
+
+import gregtech.api.enums.InventoryType;
+import gregtech.api.logic.FluidInventoryLogic;
+
+public interface FluidInventoryLogicHost extends IFluidHandler {
+
+ /**
+ * To be used for single blocks or when directly interacting with the controller
+ *
+ * @param side The side from where fluids are being inputted or extracted from
+ * @param type The type of inventory being accessed. For inputting its Input, For outputting its Output.
+ * @return The Fluid Logic responsible for said type. Can return null if the side is invalid
+ */
+ @Nullable
+ FluidInventoryLogic getFluidLogic(@Nonnull ForgeDirection side, @Nonnull InventoryType type);
+
+ /**
+ * Only to be used by MultiBlockPart for accessing the Controller Inventory
+ *
+ * @param type Type of inventory, is it Input or Output
+ * @param id ID of the locked inventory. A null id is all inventories of said controller of said type
+ * @return The Fluid Logic responsible for everything that should be done with said inventory
+ */
+ @Nonnull
+ default FluidInventoryLogic getFluidLogic(@Nonnull InventoryType type, @Nullable UUID id) {
+ return Objects.requireNonNull(getFluidLogic(ForgeDirection.UNKNOWN, type));
+ }
+
+ /**
+ * Returns an empty set if the type is {@link InventoryType#Both} or when the machine isn't a controller.
+ */
+ @Nonnull
+ default Set<Entry<UUID, FluidInventoryLogic>> getAllFluidInventoryLogics(@Nonnull InventoryType type) {
+ return new HashSet<>();
+ }
+
+ @Override
+ default boolean canDrain(@Nonnull ForgeDirection from, Fluid fluid) {
+ FluidInventoryLogic logic = getFluidLogic(from, InventoryType.Output);
+ return logic != null;
+ }
+
+ @Override
+ default boolean canFill(@Nonnull ForgeDirection from, Fluid fluid) {
+ FluidInventoryLogic logic = getFluidLogic(from, InventoryType.Input);
+ return logic != null;
+ }
+
+ @Override
+ @Nullable
+ default FluidStack drain(@Nonnull ForgeDirection from, @Nonnull FluidStack resource, boolean doDrain) {
+ FluidInventoryLogic logic = getFluidLogic(from, InventoryType.Output);
+ if (logic == null) return null;
+ return logic.drain(resource.getFluid(), resource.amount, !doDrain);
+ }
+
+ @Override
+ @Nullable
+ default FluidStack drain(@Nonnull ForgeDirection from, int maxDrain, boolean doDrain) {
+ FluidInventoryLogic logic = getFluidLogic(from, InventoryType.Output);
+ if (logic == null) return null;
+ return logic.drain(maxDrain, !doDrain);
+ }
+
+ @Override
+ default int fill(@Nonnull ForgeDirection from, @Nonnull FluidStack resource, boolean doFill) {
+ FluidInventoryLogic logic = getFluidLogic(from, InventoryType.Input);
+ if (logic == null) return 0;
+ return saturatedCast(logic.fill(resource.getFluid(), resource.amount, !doFill));
+ }
+
+ @Override
+ @Nullable
+ default FluidTankInfo[] getTankInfo(@Nonnull ForgeDirection from) {
+ return null;
+ }
+}
diff --git a/src/main/java/gregtech/api/logic/interfaces/ItemInventoryLogicHost.java b/src/main/java/gregtech/api/logic/interfaces/ItemInventoryLogicHost.java
new file mode 100644
index 0000000000..a65f3c50f1
--- /dev/null
+++ b/src/main/java/gregtech/api/logic/interfaces/ItemInventoryLogicHost.java
@@ -0,0 +1,172 @@
+package gregtech.api.logic.interfaces;
+
+import java.util.HashSet;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Set;
+import java.util.UUID;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.inventory.ISidedInventory;
+import net.minecraft.item.ItemStack;
+import net.minecraftforge.common.util.ForgeDirection;
+
+import gregtech.api.enums.InventoryType;
+import gregtech.api.logic.ItemInventoryLogic;
+
+public interface ItemInventoryLogicHost extends ISidedInventory {
+
+ /**
+ * To be used for single blocks or when directly interacting with the controller
+ *
+ * @param side The side from where items are being inputted or extracted from
+ * @param type The type of inventory being accessed. For inputting its Input, For outputting its Output.
+ * @return The Item Logic responsible for said type. Will return null if the side is not valid
+ */
+ @Nullable
+ ItemInventoryLogic getItemLogic(@Nonnull ForgeDirection side, @Nonnull InventoryType type);
+
+ /**
+ * Only to be used by MultiBlockPart for accessing the Controller Inventory
+ *
+ * @param type Type of inventory, is it Input or Output
+ * @param id ID of the locked inventory. A null id is all inventories of said controller of said type
+ * @return The Item Logic responsible for everything that should be done with said inventory
+ */
+ @Nonnull
+ default ItemInventoryLogic getItemLogic(@Nonnull InventoryType type, @Nullable UUID id) {
+ return Objects.requireNonNull(getItemLogic(ForgeDirection.UNKNOWN, type));
+ }
+
+ /**
+ * Only to be used for MultiBlockPart
+ *
+ * @return
+ */
+ @Nullable
+ default InventoryType getItemInventoryType() {
+ return null;
+ }
+
+ /**
+ * Returns an empty set if the type is {@link InventoryType#Both} or this is used when the machine isn't a
+ * controller
+ */
+ @Nonnull
+ default Set<Entry<UUID, ItemInventoryLogic>> getAllItemInventoryLogics(@Nonnull InventoryType type) {
+ return new HashSet<>();
+ }
+
+ @Override
+ @Nullable
+ default ItemStack decrStackSize(int slot, int count) {
+ InventoryType type = getItemInventoryType();
+ if (type == InventoryType.Both) return null;
+ ItemInventoryLogic logic = getItemLogic(ForgeDirection.UNKNOWN, type == null ? InventoryType.Output : type);
+ if (logic == null) return null;
+ return logic.extractItem(slot, count);
+ }
+
+ @Override
+ default int getSizeInventory() {
+ InventoryType type = getItemInventoryType();
+ if (type == InventoryType.Both) return 0;
+ ItemInventoryLogic logic = getItemLogic(ForgeDirection.UNKNOWN, type == null ? InventoryType.Output : type);
+ if (logic == null) return 0;
+ return logic.getSlots();
+ }
+
+ @Override
+ @Nullable
+ default ItemStack getStackInSlot(int slot) {
+ InventoryType type = getItemInventoryType();
+ if (type == InventoryType.Both) return null;
+ ItemInventoryLogic logic = getItemLogic(ForgeDirection.UNKNOWN, type == null ? InventoryType.Output : type);
+ if (logic == null) return null;
+ return logic.getInventory()
+ .getStackInSlot(slot);
+ }
+
+ @Override
+ default boolean isItemValidForSlot(int slot, @Nullable ItemStack stack) {
+ InventoryType type = getItemInventoryType();
+ if (type == InventoryType.Both) return false;
+ ItemInventoryLogic logic = getItemLogic(ForgeDirection.UNKNOWN, type == null ? InventoryType.Output : type);
+ if (logic == null) return false;
+ return logic.getInventory()
+ .isItemValid(slot, stack);
+ }
+
+ @Override
+ default void setInventorySlotContents(int slot, @Nullable ItemStack stack) {
+ InventoryType type = getItemInventoryType();
+ if (type == InventoryType.Both) return;
+ ItemInventoryLogic logic = getItemLogic(ForgeDirection.UNKNOWN, type == null ? InventoryType.Output : type);
+ if (logic == null) return;
+ logic.getInventory()
+ .setStackInSlot(slot, stack);
+ }
+
+ @Override
+ default boolean canExtractItem(int ignoredSlot, ItemStack ignoredItem, int side) {
+ InventoryType type = getItemInventoryType();
+ if (type == null) return false;
+ return getItemLogic(ForgeDirection.getOrientation(side), type) != null;
+ }
+
+ @Override
+ default boolean canInsertItem(int ignoredSlot, ItemStack ignoredItem, int side) {
+ InventoryType type = getItemInventoryType();
+ if (type == null) return false;
+ return getItemInventoryType() != InventoryType.Output
+ && getItemLogic(ForgeDirection.getOrientation(side), type) != null;
+ }
+
+ @Override
+ default int[] getAccessibleSlotsFromSide(int side) {
+ InventoryType type = getItemInventoryType();
+ if (type == null) return new int[0];
+ ItemInventoryLogic logic = getItemLogic(ForgeDirection.UNKNOWN, type == null ? InventoryType.Output : type);
+ if (logic == null) return new int[0];
+ int[] indexes = new int[logic.getSlots()];
+ for (int i = 0; i < logic.getSlots(); i++) {
+ indexes[i] = i;
+ }
+ return indexes;
+ }
+
+ @Override
+ default void closeInventory() {}
+
+ @Override
+ default String getInventoryName() {
+ return "";
+ }
+
+ @Override
+ default int getInventoryStackLimit() {
+ return 64;
+ }
+
+ @Override
+ default ItemStack getStackInSlotOnClosing(int index) {
+ return null;
+ }
+
+ @Override
+ default boolean hasCustomInventoryName() {
+ return false;
+ }
+
+ @Override
+ default boolean isUseableByPlayer(@Nonnull EntityPlayer player) {
+ return false;
+ }
+
+ @Override
+ default void openInventory() {}
+
+}
diff --git a/src/main/java/gregtech/api/logic/interfaces/ModelRenderLogicHost.java b/src/main/java/gregtech/api/logic/interfaces/ModelRenderLogicHost.java
new file mode 100644
index 0000000000..9a0afaa539
--- /dev/null
+++ b/src/main/java/gregtech/api/logic/interfaces/ModelRenderLogicHost.java
@@ -0,0 +1,10 @@
+package gregtech.api.logic.interfaces;
+
+import gregtech.api.logic.ModelRenderLogic;
+
+public interface ModelRenderLogicHost {
+
+ ModelRenderLogic getRenderLogic();
+
+ boolean shouldRenderModel();
+}
diff --git a/src/main/java/gregtech/api/logic/interfaces/PowerLogicHost.java b/src/main/java/gregtech/api/logic/interfaces/PowerLogicHost.java
new file mode 100644
index 0000000000..4903d7fa23
--- /dev/null
+++ b/src/main/java/gregtech/api/logic/interfaces/PowerLogicHost.java
@@ -0,0 +1,60 @@
+package gregtech.api.logic.interfaces;
+
+import java.util.Objects;
+
+import javax.annotation.Nonnull;
+
+import net.minecraftforge.common.util.ForgeDirection;
+
+import gregtech.api.interfaces.tileentity.IEnergyConnected;
+import gregtech.api.logic.PowerLogic;
+
+/**
+ * Power logic class for one to use to enable a machine to use energy
+ */
+public interface PowerLogicHost {
+
+ /**
+ *
+ * @param side Side being access to try and get the power logic from
+ * @return Can return NullPowerLogic if the side doesn't allow the return of the logic. That power logic is unusable
+ */
+ @Nonnull
+ PowerLogic getPowerLogic(@Nonnull ForgeDirection side);
+
+ /**
+ * Gives the power logic ignoring the side.
+ */
+ @Nonnull
+ default PowerLogic getPowerLogic() {
+ return Objects.requireNonNull(getPowerLogic(ForgeDirection.UNKNOWN));
+ }
+
+ /**
+ * Shortcut to the method of {@link PowerLogic#isEnergyReceiver()}
+ */
+ default boolean isEnergyReceiver() {
+ return getPowerLogic().isEnergyReceiver();
+ }
+
+ /**
+ * Shortcut to the method of {@link PowerLogic#isEnergyEmitter()}
+ */
+ default boolean isEnergyEmitter() {
+ return getPowerLogic().isEnergyEmitter();
+ }
+
+ /**
+ * Method for emitting energy to other blocks and machines. Override when it needs to be changed.
+ */
+ default void emitEnergyFromLogic() {
+ IEnergyConnected.Util.emitEnergyToNetwork(this, getPowerOutputSide());
+ }
+
+ /**
+ * From where does the machine output energy from?
+ * When the output side is {@link ForgeDirection#UNKNOWN} then it won't output energy
+ */
+ @Nonnull
+ ForgeDirection getPowerOutputSide();
+}
diff --git a/src/main/java/gregtech/api/logic/interfaces/ProcessingLogicHost.java b/src/main/java/gregtech/api/logic/interfaces/ProcessingLogicHost.java
new file mode 100644
index 0000000000..b8291c9843
--- /dev/null
+++ b/src/main/java/gregtech/api/logic/interfaces/ProcessingLogicHost.java
@@ -0,0 +1,82 @@
+package gregtech.api.logic.interfaces;
+
+import javax.annotation.Nonnull;
+
+import gregtech.api.enums.VoidingMode;
+import gregtech.api.interfaces.tileentity.IMachineProgress;
+import gregtech.api.interfaces.tileentity.IVoidable;
+import gregtech.api.logic.MuTEProcessingLogic;
+
+public interface ProcessingLogicHost<P extends MuTEProcessingLogic<P>>
+ extends IVoidable, ItemInventoryLogicHost, FluidInventoryLogicHost, IMachineProgress {
+
+ /**
+ * Get the processing logic for the current machine
+ */
+ @Nonnull
+ P getProcessingLogic();
+
+ boolean isInputSeparated();
+
+ void setInputSeparation(Boolean inputSeparation);
+
+ default boolean supportsInputSeparation() {
+ return true;
+ }
+
+ default boolean getDefaultInputSeparationMode() {
+ return false;
+ }
+
+ boolean isRecipeLockingEnabled();
+
+ void setRecipeLocking(Boolean recipeLocked);
+
+ default boolean supportsSingleRecipeLocking() {
+ return true;
+ }
+
+ default boolean getDefaultRecipeLockingMode() {
+ return false;
+ }
+
+ default boolean supportsBatchMode() {
+ return true;
+ }
+
+ void setBatchMode(Boolean batchMode);
+
+ boolean isBatchModeEnabled();
+
+ default boolean getDefaultBatchMode() {
+ return false;
+ }
+
+ /**
+ * Get what the machine can void or not
+ */
+ @Nonnull
+ VoidingMode getVoidMode();
+
+ /**
+ * Called when the processing logic should be updated by {@link #needsUpdate()}
+ */
+ default void updateProcessingLogic(@Nonnull P processingLogic) {}
+
+ /**
+ * Called before the recipe check, but after any other updates
+ */
+ default void setProcessingLogicPower(@Nonnull P processingLogic) {}
+
+ /**
+ * DO NOT CALL YOURSELF!!!
+ *
+ * If you want to make the processing logic be updated call {@link #setProcessingUpdate(boolean)}
+ */
+ boolean needsUpdate();
+
+ /**
+ * To be called when one needs to updated the processing logic. That can be when parallel changes, ect.
+ */
+ void setProcessingUpdate(boolean update);
+}