diff options
Diffstat (limited to 'src/main/java/gregtech/api/logic')
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); +} |