diff options
8 files changed, 709 insertions, 80 deletions
diff --git a/build.gradle b/build.gradle index f92e2a9a82..5f4aad8103 100644 --- a/build.gradle +++ b/build.gradle @@ -109,6 +109,8 @@ dependencies { //Soft Depths (Without this, it wont compile, not needed as library) compileOnly 'commons-io:commons-io:2.4' + compileOnly 'com.google.auto.value:auto-value-annotations:1.8.2' + annotationProcessor 'com.google.auto.value:auto-value:1.8.2' compileOnly "eu.usrv:YAMCore:${config.minecraft.version}-${config.yamcore.version}:deobf" compileOnly "tconstruct:TConstruct:${config.minecraft.version}-${config.tconstruct.version}:deobf" compileOnly "codechicken:Translocator:${config.minecraft.version}-${config.translocators.version}:dev" diff --git a/src/main/java/gregtech/api/metatileentity/implementations/GT_MetaTileEntity_MultiBlockBase.java b/src/main/java/gregtech/api/metatileentity/implementations/GT_MetaTileEntity_MultiBlockBase.java index 7a2f1bedac..6369c48f8e 100644 --- a/src/main/java/gregtech/api/metatileentity/implementations/GT_MetaTileEntity_MultiBlockBase.java +++ b/src/main/java/gregtech/api/metatileentity/implementations/GT_MetaTileEntity_MultiBlockBase.java @@ -13,6 +13,7 @@ import gregtech.api.objects.GT_ItemStack; import gregtech.api.util.GT_Log; import gregtech.api.util.GT_ModHandler; import gregtech.api.util.GT_Recipe.GT_Recipe_Map; +import gregtech.api.util.GT_Single_Recipe_Check; import gregtech.api.util.GT_Utility; import gregtech.common.GT_Pollution; import gregtech.common.items.GT_MetaGenerated_Tool_01; @@ -43,6 +44,9 @@ public abstract class GT_MetaTileEntity_MultiBlockBase extends MetaTileEntity { public int damageFactorLow = 5; public float damageFactorHigh = 0.6f; + public boolean mLockedToSingleRecipe = false; + public GT_Single_Recipe_Check mSingleRecipeCheck = null; + public ArrayList<GT_MetaTileEntity_Hatch_Input> mInputHatches = new ArrayList<>(); public ArrayList<GT_MetaTileEntity_Hatch_Output> mOutputHatches = new ArrayList<>(); public ArrayList<GT_MetaTileEntity_Hatch_InputBus> mInputBusses = new ArrayList<>(); @@ -81,6 +85,24 @@ public abstract class GT_MetaTileEntity_MultiBlockBase extends MetaTileEntity { return aSide != getBaseMetaTileEntity().getFrontFacing(); } + /** Override this if you are a multi-block that has added support for single recipe locking. */ + public boolean supportsSingleRecipeLocking() { + return false; + } + + @Override + public void onScrewdriverRightClick(byte aSide, EntityPlayer aPlayer, float aX, float aY, float aZ) { + if (supportsSingleRecipeLocking()) { + mLockedToSingleRecipe = !mLockedToSingleRecipe; + if (mLockedToSingleRecipe) { + GT_Utility.sendChatToPlayer(aPlayer, trans("219","Single recipe locking enabled. Will lock to next recipe.")); + } else { + GT_Utility.sendChatToPlayer(aPlayer, trans("220","Single recipe locking disabled.")); + mSingleRecipeCheck = null; + } + } + } + @Override public boolean isSimpleMachine() { return false; @@ -125,6 +147,7 @@ public abstract class GT_MetaTileEntity_MultiBlockBase extends MetaTileEntity { aNBT.setInteger("mEfficiency", mEfficiency); aNBT.setInteger("mPollution", mPollution); aNBT.setInteger("mRuntime", mRuntime); + aNBT.setBoolean("mLockedToSingleRecipe", mLockedToSingleRecipe); if (mOutputItems != null) { aNBT.setInteger("mOutputItemsLength", mOutputItems.length); @@ -162,6 +185,7 @@ public abstract class GT_MetaTileEntity_MultiBlockBase extends MetaTileEntity { mEfficiency = aNBT.getInteger("mEfficiency"); mPollution = aNBT.getInteger("mPollution"); mRuntime = aNBT.getInteger("mRuntime"); + mLockedToSingleRecipe = aNBT.getBoolean("mLockedToSingleRecipe"); int aOutputItemsLength = aNBT.getInteger("mOutputItemsLength"); if (aOutputItemsLength > 0) { diff --git a/src/main/java/gregtech/api/util/GT_LanguageManager.java b/src/main/java/gregtech/api/util/GT_LanguageManager.java index 9faa7b11a6..767c830f79 100644 --- a/src/main/java/gregtech/api/util/GT_LanguageManager.java +++ b/src/main/java/gregtech/api/util/GT_LanguageManager.java @@ -335,6 +335,8 @@ public class GT_LanguageManager { addStringLocalization("Interaction_DESCRIPTION_Index_216", "Deprecated Recipe"); addStringLocalization("Interaction_DESCRIPTION_Index_217", "Stocking mode. Keeps this many items in destination input slots. This mode can be server unfriendly."); addStringLocalization("Interaction_DESCRIPTION_Index_218", "Transfer size mode. Add exactly this many items in destination input slots as long as there is room."); + addStringLocalization("Interaction_DESCRIPTION_Index_219", "Single recipe locking enabled. Will lock to next recipe."); + addStringLocalization("Interaction_DESCRIPTION_Index_220", "Single recipe locking disabled."); addStringLocalization("Interaction_DESCRIPTION_Index_500", "Fitting: Loose - More Flow"); addStringLocalization("Interaction_DESCRIPTION_Index_501", "Fitting: Tight - More Efficiency"); diff --git a/src/main/java/gregtech/api/util/GT_Single_Recipe_Check.java b/src/main/java/gregtech/api/util/GT_Single_Recipe_Check.java new file mode 100644 index 0000000000..469f2d64a1 --- /dev/null +++ b/src/main/java/gregtech/api/util/GT_Single_Recipe_Check.java @@ -0,0 +1,284 @@ +package gregtech.api.util; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; +import gregtech.api.metatileentity.implementations.GT_MetaTileEntity_MultiBlockBase; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraftforge.fluids.Fluid; +import net.minecraftforge.fluids.FluidStack; + +import javax.annotation.Nullable; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** Used by machines that are locked to a single recipe, for fast computation. */ +public class GT_Single_Recipe_Check { + protected final GT_MetaTileEntity_MultiBlockBase multiBlockBase; + protected final GT_Recipe recipe; + protected final ImmutableMap<ItemId, Integer> itemCost; + protected final ImmutableMap<Fluid, Integer> fluidCost; + + protected final int totalItemCost; + protected final int totalFluidCost; + + protected GT_Single_Recipe_Check( + GT_MetaTileEntity_MultiBlockBase multiBlockBase, + GT_Recipe recipe, + ImmutableMap<ItemId, Integer> itemCost, + ImmutableMap<Fluid, Integer> fluidCost) { + this.multiBlockBase = multiBlockBase; + this.recipe = recipe; + this.itemCost = itemCost; + this.fluidCost = fluidCost; + + this.totalItemCost = itemCost.values().stream().mapToInt(Integer::intValue).sum(); + this.totalFluidCost = fluidCost.values().stream().mapToInt(Integer::intValue).sum(); + } + + public GT_Recipe getRecipe() { + return recipe; + } + + /** + * Use this method if recipes cannot require more than a single stack of any item or fluid. + * In particular, {@link GT_MetaTileEntity_MultiBlockBase#getCompactedInputs()} and + * {@link GT_MetaTileEntity_MultiBlockBase#getCompactedFluids()} both enforce this single-stack + * restriction, so any multi-block that calls those can use this method. + */ + public boolean checkRecipeInputsSingleStack(boolean consumeInputs) { + Map<ItemId, ItemStack> itemMap = null; + if (totalItemCost > 0) { + itemMap = new HashMap<>(); + for (ItemStack itemStack : multiBlockBase.getStoredInputs()) { + itemMap.merge( + ItemId.createNoCopy(itemStack), itemStack, + (a, b) -> a.stackSize >= b.stackSize ? a : b); + } + + for (Map.Entry<ItemId, Integer> entry : itemCost.entrySet()) { + ItemStack itemStack = itemMap.get(entry.getKey()); + if (itemStack == null || itemStack.stackSize < entry.getValue()) { + return false; + } + } + } + + Map<Fluid, FluidStack> fluidMap = null; + if (totalFluidCost > 0) { + fluidMap = new HashMap<>(); + for (FluidStack fluidStack : multiBlockBase.getStoredFluids()) { + fluidMap.merge( + fluidStack.getFluid(), fluidStack, + (a, b) -> a.amount >= b.amount ? a : b); + } + + for (Map.Entry<Fluid, Integer> entry : fluidCost.entrySet()) { + FluidStack fluidStack = fluidMap.get(entry.getKey()); + if (fluidStack == null || fluidStack.amount < entry.getValue()) { + return false; + } + } + } + + if (consumeInputs) { + if (totalItemCost > 0) { + for (Map.Entry<ItemId, Integer> entry : itemCost.entrySet()) { + itemMap.get(entry.getKey()).stackSize -= entry.getValue(); + } + } + + if (totalFluidCost > 0) { + for (Map.Entry<Fluid, Integer> entry : fluidCost.entrySet()) { + fluidMap.get(entry.getKey()).amount -= entry.getValue(); + } + } + } + + return true; + } + + /** + * Use this method if recipes can require more than a single stack of any item or fluid. + * It is less efficient, though. + */ + public boolean checkRecipeInputs(boolean consumeInputs) { + List<ItemStack> items = null; + if (totalItemCost > 0) { + items = multiBlockBase.getStoredInputs(); + + Map<ItemId, Integer> itemMap = new HashMap<>(); + for (ItemStack itemStack : items) { + itemMap.merge(ItemId.createNoCopy(itemStack), itemStack.stackSize, Integer::sum); + } + + for (Map.Entry<ItemId, Integer> entry : itemCost.entrySet()) { + if (itemMap.getOrDefault(entry.getKey(), 0) < entry.getValue()) { + return false; + } + } + } + + List<FluidStack> fluids = null; + if (totalFluidCost > 0) { + fluids = multiBlockBase.getStoredFluids(); + + Map<Fluid, Integer> fluidMap = new HashMap<>(); + for (FluidStack fluidStack : fluids) { + fluidMap.merge(fluidStack.getFluid(), fluidStack.amount, Integer::sum); + } + + for (Map.Entry<Fluid, Integer> entry : fluidCost.entrySet()) { + if (fluidMap.getOrDefault(entry.getKey(), 0) < entry.getValue()) { + return false; + } + } + } + + if (consumeInputs) { + if (totalItemCost > 0) { + int remainingItemCost = totalItemCost; + Map<ItemId, Integer> runningItemCost = Maps.newHashMap(itemCost); + for (ItemStack itemStack : items) { + ItemId key = ItemId.createNoCopy(itemStack); + int runningCost = runningItemCost.getOrDefault(key, 0); + int paid = Math.min(itemStack.stackSize, runningCost); + itemStack.stackSize -= paid; + runningItemCost.put(key, runningCost - paid); + + remainingItemCost -= paid; + if (remainingItemCost <= 0) { + break; + } + } + } + + if (totalFluidCost > 0) { + int remainingFluidCost = totalFluidCost; + Map<Fluid, Integer> runningFluidCost = Maps.newHashMap(fluidCost); + for (FluidStack fluidStack : fluids) { + Fluid key = fluidStack.getFluid(); + int runningCost = runningFluidCost.getOrDefault(key, 0); + int paid = Math.min(fluidStack.amount, runningCost); + fluidStack.amount -= paid; + runningFluidCost.put(key, runningCost - paid); + + remainingFluidCost -= paid; + if (remainingFluidCost <= 0) { + break; + } + } + } + } + + return true; + } + + protected static Map<ItemId, Integer> buildItemMap( + GT_MetaTileEntity_MultiBlockBase multiBlockBase) { + Map<ItemId, Integer> itemMap = new HashMap<>(); + for (ItemStack itemStack : multiBlockBase.getStoredInputs()) { + itemMap.merge(ItemId.create(itemStack), itemStack.stackSize, Integer::sum); + } + return itemMap; + } + + protected static Map<Fluid, Integer> buildFluidMap( + GT_MetaTileEntity_MultiBlockBase multiBlockBase) { + Map<Fluid, Integer> fluidMap = new HashMap<>(); + for (FluidStack fluidStack : multiBlockBase.getStoredFluids()) { + fluidMap.merge(fluidStack.getFluid(), fluidStack.amount, Integer::sum); + } + return fluidMap; + } + + public static Builder builder(GT_MetaTileEntity_MultiBlockBase multiBlockBase) { + return new Builder(multiBlockBase); + } + + public static final class Builder { + private final GT_MetaTileEntity_MultiBlockBase multiBlockBase; + + // In order to compute which items and fluids are consumed by the recipe, we compare the + // multi-block's items and fluids before and after inputs are consumed by the recipe. + private Map<ItemId, Integer> beforeItems; + private Map<Fluid, Integer> beforeFluids; + private Map<ItemId, Integer> afterItems; + private Map<Fluid, Integer> afterFluids; + + private GT_Recipe recipe; + + private Builder(GT_MetaTileEntity_MultiBlockBase multiBlockBase) { + this.multiBlockBase = multiBlockBase; + } + + /** Call this before inputs are consumed by the recipe. */ + public Builder setBefore() { + beforeItems = buildItemMap(multiBlockBase); + beforeFluids = buildFluidMap(multiBlockBase); + return this; + } + + /** Call this after inputs are consumed by the recipe. */ + public Builder setAfter() { + afterItems = buildItemMap(multiBlockBase); + afterFluids = buildFluidMap(multiBlockBase); + return this; + } + + public Builder setRecipe(GT_Recipe recipe) { + this.recipe = recipe; + return this; + } + + public GT_Single_Recipe_Check build() { + ImmutableMap.Builder<ItemId, Integer> itemCostBuilder = ImmutableMap.builder(); + for (Map.Entry<ItemId, Integer> entry : beforeItems.entrySet()) { + int cost = entry.getValue() - afterItems.getOrDefault(entry.getKey(), 0); + if (cost > 0) { + itemCostBuilder.put(entry.getKey(), cost); + } + } + + ImmutableMap.Builder<Fluid, Integer> fluidCostBuilder = ImmutableMap.builder(); + for (Map.Entry<Fluid, Integer> entry : beforeFluids.entrySet()) { + int cost = entry.getValue() - afterFluids.getOrDefault(entry.getKey(), 0); + if (cost > 0) { + fluidCostBuilder.put(entry.getKey(), cost); + } + } + + return new GT_Single_Recipe_Check( + multiBlockBase, recipe, itemCostBuilder.build(), fluidCostBuilder.build()); + } + } + + @AutoValue + protected abstract static class ItemId { + /** This method copies NBT, as it is mutable. */ + protected static ItemId create(ItemStack itemStack) { + NBTTagCompound nbt = itemStack.getTagCompound(); + if (nbt != null) { + nbt = (NBTTagCompound) nbt.copy(); + } + + return new AutoValue_GT_Single_Recipe_Check_ItemId( + itemStack.getItem(), itemStack.getItemDamage(), nbt); + } + + /** This method does not copy NBT in order to save time. Make sure not to mutate it! */ + protected static ItemId createNoCopy(ItemStack itemStack) { + return new AutoValue_GT_Single_Recipe_Check_ItemId( + itemStack.getItem(), itemStack.getItemDamage(), itemStack.getTagCompound()); + } + + protected abstract Item item(); + protected abstract int metaData(); + + @Nullable + protected abstract NBTTagCompound nbt(); + } +} diff --git a/src/main/java/gregtech/api/util/GT_Single_Recipe_Check_Processing_Array.java b/src/main/java/gregtech/api/util/GT_Single_Recipe_Check_Processing_Array.java new file mode 100644 index 0000000000..225e0bd723 --- /dev/null +++ b/src/main/java/gregtech/api/util/GT_Single_Recipe_Check_Processing_Array.java @@ -0,0 +1,212 @@ +package gregtech.api.util; + +import com.google.common.collect.ImmutableMap; +import gregtech.api.metatileentity.implementations.GT_MetaTileEntity_MultiBlockBase; +import net.minecraft.item.ItemStack; +import net.minecraftforge.fluids.Fluid; +import net.minecraftforge.fluids.FluidStack; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** Processing Array-specialized version of {@link gregtech.api.util.GT_Single_Recipe_Check}. */ +public class GT_Single_Recipe_Check_Processing_Array extends GT_Single_Recipe_Check { + protected final int recipeAmperage; + + /** + * The machine type that the locked recipe is for. + * We will not allow running with other machines. + */ + protected final ItemStack machineStack; + + protected GT_Single_Recipe_Check_Processing_Array( + GT_MetaTileEntity_MultiBlockBase multiBlockBase, + GT_Recipe recipe, + ImmutableMap<ItemId, Integer> itemCost, + ImmutableMap<Fluid, Integer> fluidCost, + int recipeAmperage, + ItemStack machineStack) { + super(multiBlockBase, recipe, itemCost, fluidCost); + + this.recipeAmperage = recipeAmperage; + this.machineStack = machineStack; + } + + public int getRecipeAmperage() { + return recipeAmperage; + } + + @Override + public boolean checkRecipeInputsSingleStack(boolean consumeInputs) { + throw new UnsupportedOperationException("Use checkRecipeInputs(boolean, int) instead."); + } + + @Override + public boolean checkRecipeInputs(boolean consumeInputs) { + throw new UnsupportedOperationException("Use checkRecipeInputs(boolean, int) instead."); + } + + /** Returns the number of parallel recipes, or 0 if recipe is not satisfied at all. */ + public int checkRecipeInputs(boolean consumeInputs, int maxParallel) { + if (!GT_Utility.areStacksEqual(machineStack, multiBlockBase.mInventory[1])) { + // Machine stack was modified. This is not allowed, so stop processing. + return 0; + } + int parallel = maxParallel; + + List<ItemStack> items = null; + if (totalItemCost > 0) { + items = multiBlockBase.getStoredInputs(); + + Map<ItemId, Integer> itemMap = new HashMap<>(); + for (ItemStack itemStack : items) { + itemMap.merge(ItemId.createNoCopy(itemStack), itemStack.stackSize, Integer::sum); + } + + for (Map.Entry<ItemId, Integer> entry : itemCost.entrySet()) { + parallel = Math.min(parallel, itemMap.getOrDefault(entry.getKey(), 0) / entry.getValue()); + if (parallel <= 0) { + return 0; + } + } + } + + List<FluidStack> fluids = null; + if (totalFluidCost > 0) { + fluids = multiBlockBase.getStoredFluids(); + + Map<Fluid, Integer> fluidMap = new HashMap<>(); + for (FluidStack fluidStack : fluids) { + fluidMap.merge(fluidStack.getFluid(), fluidStack.amount, Integer::sum); + } + + for (Map.Entry<Fluid, Integer> entry : fluidCost.entrySet()) { + parallel = Math.min(parallel, fluidMap.getOrDefault(entry.getKey(), 0) / entry.getValue()); + if (parallel <= 0) { + return 0; + } + } + } + + final int finalParallel = parallel; + if (consumeInputs) { + if (totalItemCost > 0) { + int remainingItemCost = totalItemCost * finalParallel; + Map<ItemId, Integer> runningItemCost = + itemCost.entrySet().stream() + .collect( + Collectors.toMap( + Map.Entry::getKey, + entry -> entry.getValue() * finalParallel)); + + for (ItemStack itemStack : items) { + ItemId key = ItemId.createNoCopy(itemStack); + int runningCost = runningItemCost.getOrDefault(key, 0); + int paid = Math.min(itemStack.stackSize, runningCost); + itemStack.stackSize -= paid; + runningItemCost.put(key, runningCost - paid); + + remainingItemCost -= paid; + if (remainingItemCost <= 0) { + break; + } + } + } + + if (totalFluidCost > 0) { + int remainingFluidCost = totalFluidCost * finalParallel; + Map<Fluid, Integer> runningFluidCost = + fluidCost.entrySet().stream() + .collect( + Collectors.toMap( + Map.Entry::getKey, + entry -> entry.getValue() * finalParallel)); + + for (FluidStack fluidStack : fluids) { + Fluid key = fluidStack.getFluid(); + int runningCost = runningFluidCost.getOrDefault(key, 0); + int paid = Math.min(fluidStack.amount, runningCost); + fluidStack.amount -= paid; + runningFluidCost.put(key, runningCost - paid); + + remainingFluidCost -= paid; + if (remainingFluidCost <= 0) { + break; + } + } + } + } + + return finalParallel; + } + + public static Builder processingArrayBuilder(GT_MetaTileEntity_MultiBlockBase multiBlockBase) { + return new Builder(multiBlockBase); + } + + public static final class Builder { + private final GT_MetaTileEntity_MultiBlockBase multiBlockBase; + + // In order to compute which items and fluids are consumed by the recipe, we compare the + // multi-block's items and fluids before and after inputs are consumed by the recipe. + private Map<ItemId, Integer> beforeItems; + private Map<Fluid, Integer> beforeFluids; + private Map<ItemId, Integer> afterItems; + private Map<Fluid, Integer> afterFluids; + + private GT_Recipe recipe; + private int recipeAmperage; + + private Builder(GT_MetaTileEntity_MultiBlockBase multiBlockBase) { + this.multiBlockBase = multiBlockBase; + } + + /** Call this before inputs are consumed by the recipe. */ + public Builder setBefore() { + beforeItems = buildItemMap(multiBlockBase); + beforeFluids = buildFluidMap(multiBlockBase); + return this; + } + + /** Call this after inputs are consumed by the recipe. */ + public Builder setAfter() { + afterItems = buildItemMap(multiBlockBase); + afterFluids = buildFluidMap(multiBlockBase); + return this; + } + + public Builder setRecipe(GT_Recipe recipe) { + this.recipe = recipe; + return this; + } + + public Builder setRecipeAmperage(int recipeAmperage) { + this.recipeAmperage = recipeAmperage; + return this; + } + + public GT_Single_Recipe_Check_Processing_Array build() { + ImmutableMap.Builder<ItemId, Integer> itemCostBuilder = ImmutableMap.builder(); + for (Map.Entry<ItemId, Integer> entry : beforeItems.entrySet()) { + int cost = entry.getValue() - afterItems.getOrDefault(entry.getKey(), 0); + if (cost > 0) { + itemCostBuilder.put(entry.getKey(), cost); + } + } + + ImmutableMap.Builder<Fluid, Integer> fluidCostBuilder = ImmutableMap.builder(); + for (Map.Entry<Fluid, Integer> entry : beforeFluids.entrySet()) { + int cost = entry.getValue() - afterFluids.getOrDefault(entry.getKey(), 0); + if (cost > 0) { + fluidCostBuilder.put(entry.getKey(), cost); + } + } + + return new GT_Single_Recipe_Check_Processing_Array( + multiBlockBase, recipe, itemCostBuilder.build(), fluidCostBuilder.build(), + recipeAmperage, multiBlockBase.mInventory[1].copy()); + } + } +}
\ No newline at end of file diff --git a/src/main/java/gregtech/common/tileentities/machines/multi/GT_MetaTileEntity_LargeChemicalReactor.java b/src/main/java/gregtech/common/tileentities/machines/multi/GT_MetaTileEntity_LargeChemicalReactor.java index 63b536fc72..87eddb7e13 100644 --- a/src/main/java/gregtech/common/tileentities/machines/multi/GT_MetaTileEntity_LargeChemicalReactor.java +++ b/src/main/java/gregtech/common/tileentities/machines/multi/GT_MetaTileEntity_LargeChemicalReactor.java @@ -11,6 +11,7 @@ import gregtech.api.metatileentity.implementations.GT_MetaTileEntity_EnhancedMul import gregtech.api.render.TextureFactory; import gregtech.api.util.GT_Multiblock_Tooltip_Builder; import gregtech.api.util.GT_Recipe; +import gregtech.api.util.GT_Single_Recipe_Check; import gregtech.api.util.GT_Utility; import net.minecraft.entity.player.InventoryPlayer; import net.minecraft.item.ItemStack; @@ -123,68 +124,98 @@ public class GT_MetaTileEntity_LargeChemicalReactor extends GT_MetaTileEntity_En } @Override + public boolean supportsSingleRecipeLocking() { + return true; + } + + @Override public boolean checkRecipe(ItemStack aStack) { - ArrayList<ItemStack> tInputList = getStoredInputs(); - int tInputList_sS = tInputList.size(); - for (int i = 0; i < tInputList_sS - 1; i++) { - for (int j = i + 1; j < tInputList_sS; j++) { - if (GT_Utility.areStacksEqual(tInputList.get(i), tInputList.get(j))) { - if (tInputList.get(i).stackSize >= tInputList.get(j).stackSize) { - tInputList.remove(j--); - tInputList_sS = tInputList.size(); - } else { - tInputList.remove(i--); - tInputList_sS = tInputList.size(); - break; + long tVoltage = getMaxInputVoltage(); + byte tier = (byte) Math.max(1, GT_Utility.getTier(tVoltage)); + GT_Recipe tRecipe; + + if (mLockedToSingleRecipe && mSingleRecipeCheck != null) { + if (!mSingleRecipeCheck.checkRecipeInputsSingleStack(true)) { + return false; + } + + tRecipe = mSingleRecipeCheck.getRecipe(); + } else { + ArrayList<ItemStack> tInputList = getStoredInputs(); + int tInputList_sS = tInputList.size(); + for (int i = 0; i < tInputList_sS - 1; i++) { + for (int j = i + 1; j < tInputList_sS; j++) { + if (GT_Utility.areStacksEqual(tInputList.get(i), tInputList.get(j))) { + if (tInputList.get(i).stackSize >= tInputList.get(j).stackSize) { + tInputList.remove(j--); + tInputList_sS = tInputList.size(); + } else { + tInputList.remove(i--); + tInputList_sS = tInputList.size(); + break; + } } } } - } - tInputList.add(mInventory[1]); - ItemStack[] inputs = tInputList.toArray(new ItemStack[0]); - - ArrayList<FluidStack> tFluidList = getStoredFluids(); - int tFluidList_sS = tFluidList.size(); - for (int i = 0; i < tFluidList_sS - 1; i++) { - for (int j = i + 1; j < tFluidList_sS; j++) { - if (GT_Utility.areFluidsEqual(tFluidList.get(i), tFluidList.get(j))) { - if (tFluidList.get(i).amount >= tFluidList.get(j).amount) { - tFluidList.remove(j--); - tFluidList_sS = tFluidList.size(); - } else { - tFluidList.remove(i--); - tFluidList_sS = tFluidList.size(); - break; + tInputList.add(mInventory[1]); + ItemStack[] inputs = tInputList.toArray(new ItemStack[0]); + + ArrayList<FluidStack> tFluidList = getStoredFluids(); + int tFluidList_sS = tFluidList.size(); + for (int i = 0; i < tFluidList_sS - 1; i++) { + for (int j = i + 1; j < tFluidList_sS; j++) { + if (GT_Utility.areFluidsEqual(tFluidList.get(i), tFluidList.get(j))) { + if (tFluidList.get(i).amount >= tFluidList.get(j).amount) { + tFluidList.remove(j--); + tFluidList_sS = tFluidList.size(); + } else { + tFluidList.remove(i--); + tFluidList_sS = tFluidList.size(); + break; + } } } } - } - FluidStack[] fluids = tFluidList.toArray(new FluidStack[0]); + FluidStack[] fluids = tFluidList.toArray(new FluidStack[0]); + + if (inputs.length == 0 && fluids.length == 0) { + return false; + } - if (inputs.length > 0 || fluids.length > 0) { - long tVoltage = getMaxInputVoltage(); - byte tier = (byte) Math.max(1, GT_Utility.getTier(tVoltage)); - GT_Recipe tRecipe = GT_Recipe.GT_Recipe_Map.sMultiblockChemicalRecipes.findRecipe(getBaseMetaTileEntity(), false, + GT_Single_Recipe_Check.Builder tSingleRecipeCheckBuilder = null; + if (mLockedToSingleRecipe) { + // We're locked to a single recipe, but haven't built the recipe checker yet. + // Build the checker on next successful recipe. + tSingleRecipeCheckBuilder = GT_Single_Recipe_Check.builder(this).setBefore(); + } + + tRecipe = GT_Recipe.GT_Recipe_Map.sMultiblockChemicalRecipes.findRecipe(getBaseMetaTileEntity(), false, false, gregtech.api.enums.GT_Values.V[tier], fluids, inputs); - if (tRecipe != null && tRecipe.isRecipeInputEqual(true, fluids, inputs)) { - this.mEfficiency = (10000 - (getIdealStatus() - getRepairStatus()) * 1000); - this.mEfficiencyIncrease = 10000; - - calculatePerfectOverclockedNessMulti(tRecipe.mEUt, tRecipe.mDuration, 1, tVoltage); - //In case recipe is too OP for that machine - if (mMaxProgresstime == Integer.MAX_VALUE - 1 && mEUt == Integer.MAX_VALUE - 1) - return false; - if (this.mEUt > 0) { - this.mEUt = (-this.mEUt); - } - this.mOutputItems = tRecipe.mOutputs; - this.mOutputFluids = tRecipe.mFluidOutputs; - this.updateSlots(); - return true; + if (tRecipe == null || !tRecipe.isRecipeInputEqual(true, fluids, inputs)) { + return false; + } + + if (mLockedToSingleRecipe) { + mSingleRecipeCheck = tSingleRecipeCheckBuilder.setAfter().setRecipe(tRecipe).build(); } } - return false; + + this.mEfficiency = (10000 - (getIdealStatus() - getRepairStatus()) * 1000); + this.mEfficiencyIncrease = 10000; + + calculatePerfectOverclockedNessMulti(tRecipe.mEUt, tRecipe.mDuration, 1, tVoltage); + //In case recipe is too OP for that machine + if (mMaxProgresstime == Integer.MAX_VALUE - 1 && mEUt == Integer.MAX_VALUE - 1) + return false; + if (this.mEUt > 0) { + this.mEUt = (-this.mEUt); + } + + this.mOutputItems = tRecipe.mOutputs; + this.mOutputFluids = tRecipe.mFluidOutputs; + this.updateSlots(); + return true; } @Override diff --git a/src/main/java/gregtech/common/tileentities/machines/multi/GT_MetaTileEntity_ProcessingArray.java b/src/main/java/gregtech/common/tileentities/machines/multi/GT_MetaTileEntity_ProcessingArray.java index ad0f5bd7f8..a48f63c60a 100644 --- a/src/main/java/gregtech/common/tileentities/machines/multi/GT_MetaTileEntity_ProcessingArray.java +++ b/src/main/java/gregtech/common/tileentities/machines/multi/GT_MetaTileEntity_ProcessingArray.java @@ -19,6 +19,7 @@ import gregtech.api.util.GT_Multiblock_Tooltip_Builder; import gregtech.api.util.GT_ProcessingArray_Manager; import gregtech.api.util.GT_Recipe; import gregtech.api.util.GT_Recipe.GT_Recipe_Map; +import gregtech.api.util.GT_Single_Recipe_Check_Processing_Array; import gregtech.api.util.GT_Utility; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.InventoryPlayer; @@ -145,7 +146,16 @@ public class GT_MetaTileEntity_ProcessingArray extends GT_MetaTileEntity_CubicMu } @Override + public boolean supportsSingleRecipeLocking() { + return true; + } + + @Override public boolean checkRecipe(ItemStack aStack) { + if (mLockedToSingleRecipe && mSingleRecipeCheck != null) { + return processLockedRecipe(); + } + if (!isCorrectMachinePart(mInventory[1])) { return false; } @@ -228,6 +238,15 @@ public class GT_MetaTileEntity_ProcessingArray extends GT_MetaTileEntity_CubicMu return false; } + public boolean processLockedRecipe() { + GT_Single_Recipe_Check_Processing_Array tSingleRecipeCheck = (GT_Single_Recipe_Check_Processing_Array) mSingleRecipeCheck; + + int machines = Math.min(64, mInventory[1].stackSize << mMult); //Upped max Cap to 64 + int parallel = tSingleRecipeCheck.checkRecipeInputs(true, machines); + + return processRecipeOutputs(tSingleRecipeCheck.getRecipe(), tSingleRecipeCheck.getRecipeAmperage(), parallel); + } + public boolean processRecipe(ItemStack[] tInputs, FluidStack[] tFluids, GT_Recipe.GT_Recipe_Map map) { if (tInputs.length <= 0 && tFluids.length <= 0) return false; GT_Recipe tRecipe = map.findRecipe(getBaseMetaTileEntity(), mLastRecipe, false, gregtech.api.enums.GT_Values.V[tTier], tFluids, tInputs); @@ -236,54 +255,77 @@ public class GT_MetaTileEntity_ProcessingArray extends GT_MetaTileEntity_CubicMu !isValidForLowGravity(tRecipe, getBaseMetaTileEntity().getWorld().provider.dimensionId)) return false; + GT_Single_Recipe_Check_Processing_Array.Builder tSingleRecipeCheckBuilder = null; + if (mLockedToSingleRecipe) { + // We're locked to a single recipe, but haven't built the recipe checker yet. + // Build the checker on next successful recipe. + tSingleRecipeCheckBuilder = GT_Single_Recipe_Check_Processing_Array.processingArrayBuilder(this).setBefore(); + } + + boolean recipeLocked = false; mLastRecipe = tRecipe; - this.mEUt = 0; - this.mOutputItems = null; - this.mOutputFluids = null; int machines = Math.min(64, mInventory[1].stackSize << mMult); //Upped max Cap to 64 int i = 0; for (; i < machines; i++) { if (!tRecipe.isRecipeInputEqual(true, tFluids, tInputs)) { - if (i == 0) { - return false; - } break; + } else if (mLockedToSingleRecipe && !recipeLocked) { + // We want to lock to a single run of the recipe. + mSingleRecipeCheck = + tSingleRecipeCheckBuilder + .setAfter() + .setRecipe(tRecipe) + .setRecipeAmperage(map.mAmperage) + .build(); + recipeLocked = true; } } - this.mMaxProgresstime = tRecipe.mDuration; + + return processRecipeOutputs(tRecipe, map.mAmperage, i); + } + + public boolean processRecipeOutputs(GT_Recipe aRecipe, int aAmperage, int parallel) { + this.mEUt = 0; + this.mOutputItems = null; + this.mOutputFluids = null; + if (parallel == 0) { + return false; + } + + this.mMaxProgresstime = aRecipe.mDuration; this.mEfficiency = (10000 - (getIdealStatus() - getRepairStatus()) * 1000); this.mEfficiencyIncrease = 10000; - calculateOverclockedNessMulti(tRecipe.mEUt, tRecipe.mDuration, map.mAmperage, GT_Values.V[tTier]); + calculateOverclockedNessMulti(aRecipe.mEUt, aRecipe.mDuration, aAmperage, GT_Values.V[tTier]); //In case recipe is too OP for that machine if (mMaxProgresstime == Integer.MAX_VALUE - 1 && mEUt == Integer.MAX_VALUE - 1) return false; - this.mEUt = GT_Utility.safeInt(((long) this.mEUt * i) >> mMult, 1); + this.mEUt = GT_Utility.safeInt(((long) this.mEUt * parallel) >> mMult, 1); if (mEUt == Integer.MAX_VALUE - 1) return false; if (this.mEUt > 0) { this.mEUt = (-this.mEUt); } - ItemStack[] tOut = new ItemStack[tRecipe.mOutputs.length]; - for (int h = 0; h < tRecipe.mOutputs.length; h++) { - if (tRecipe.getOutput(h) != null) { - tOut[h] = tRecipe.getOutput(h).copy(); + ItemStack[] tOut = new ItemStack[aRecipe.mOutputs.length]; + for (int h = 0; h < aRecipe.mOutputs.length; h++) { + if (aRecipe.getOutput(h) != null) { + tOut[h] = aRecipe.getOutput(h).copy(); tOut[h].stackSize = 0; } } FluidStack tFOut = null; - if (tRecipe.getFluidOutput(0) != null) tFOut = tRecipe.getFluidOutput(0).copy(); + if (aRecipe.getFluidOutput(0) != null) tFOut = aRecipe.getFluidOutput(0).copy(); for (int f = 0; f < tOut.length; f++) { - if (tRecipe.mOutputs[f] != null && tOut[f] != null) { - for (int g = 0; g < i; g++) { - if (getBaseMetaTileEntity().getRandomNumber(10000) < tRecipe.getOutputChance(f)) - tOut[f].stackSize += tRecipe.mOutputs[f].stackSize; + if (aRecipe.mOutputs[f] != null && tOut[f] != null) { + for (int g = 0; g < parallel; g++) { + if (getBaseMetaTileEntity().getRandomNumber(10000) < aRecipe.getOutputChance(f)) + tOut[f].stackSize += aRecipe.mOutputs[f].stackSize; } } } if (tFOut != null) { int tSize = tFOut.amount; - tFOut.amount = tSize * i; + tFOut.amount = tSize * parallel; } this.mMaxProgresstime = Math.max(1, this.mMaxProgresstime); this.mOutputItems = Arrays.stream(tOut) @@ -353,8 +395,13 @@ public class GT_MetaTileEntity_ProcessingArray extends GT_MetaTileEntity_CubicMu @Override public final void onScrewdriverRightClick(byte aSide, EntityPlayer aPlayer, float aX, float aY, float aZ) { - mSeparate = !mSeparate; - GT_Utility.sendChatToPlayer(aPlayer, StatCollector.translateToLocal("GT5U.machines.separatebus") + " " + mSeparate); + if (aPlayer.isSneaking()) { + // Lock to single recipe + super.onScrewdriverRightClick(aSide, aPlayer, aX, aY, aZ); + } else { + mSeparate = !mSeparate; + GT_Utility.sendChatToPlayer(aPlayer, StatCollector.translateToLocal("GT5U.machines.separatebus") + " " + mSeparate); + } } @Override diff --git a/src/main/java/gregtech/common/tileentities/machines/multi/GT_MetaTileEntity_PyrolyseOven.java b/src/main/java/gregtech/common/tileentities/machines/multi/GT_MetaTileEntity_PyrolyseOven.java index ae96b82a23..9542c54ce0 100644 --- a/src/main/java/gregtech/common/tileentities/machines/multi/GT_MetaTileEntity_PyrolyseOven.java +++ b/src/main/java/gregtech/common/tileentities/machines/multi/GT_MetaTileEntity_PyrolyseOven.java @@ -16,6 +16,7 @@ import gregtech.api.metatileentity.implementations.GT_MetaTileEntity_EnhancedMul import gregtech.api.render.TextureFactory; import gregtech.api.util.GT_Multiblock_Tooltip_Builder; import gregtech.api.util.GT_Recipe; +import gregtech.api.util.GT_Single_Recipe_Check; import gregtech.api.util.GT_Utility; import net.minecraft.entity.player.InventoryPlayer; import net.minecraft.item.ItemStack; @@ -129,19 +130,45 @@ public class GT_MetaTileEntity_PyrolyseOven extends GT_MetaTileEntity_EnhancedMu } @Override - public boolean checkRecipe(ItemStack aStack) { - ItemStack[] tInputs = getCompactedInputs(); - FluidStack[] tFluids = getCompactedFluids(); - - if (tInputs.length <= 0) - return false; + public boolean supportsSingleRecipeLocking() { + return true; + } + @Override + public boolean checkRecipe(ItemStack aStack) { long tVoltage = getMaxInputVoltage(); byte tTier = (byte) Math.max(1, GT_Utility.getTier(tVoltage)); - GT_Recipe tRecipe = GT_Recipe.GT_Recipe_Map.sPyrolyseRecipes.findRecipe(getBaseMetaTileEntity(), false, gregtech.api.enums.GT_Values.V[tTier], tFluids, tInputs); + GT_Recipe tRecipe; - if (tRecipe == null || !tRecipe.isRecipeInputEqual(true, tFluids, tInputs)) - return false; + if (mLockedToSingleRecipe && mSingleRecipeCheck != null) { + if (!mSingleRecipeCheck.checkRecipeInputsSingleStack(true)) { + return false; + } + + tRecipe = mSingleRecipeCheck.getRecipe(); + } else { + ItemStack[] tInputs = getCompactedInputs(); + FluidStack[] tFluids = getCompactedFluids(); + + if (tInputs.length <= 0) + return false; + + GT_Single_Recipe_Check.Builder tSingleRecipeCheckBuilder = null; + if (mLockedToSingleRecipe) { + // We're locked to a single recipe, but haven't built the recipe checker yet. + // Build the checker on next successful recipe. + tSingleRecipeCheckBuilder = GT_Single_Recipe_Check.builder(this).setBefore(); + } + + tRecipe = GT_Recipe.GT_Recipe_Map.sPyrolyseRecipes.findRecipe(getBaseMetaTileEntity(), false, gregtech.api.enums.GT_Values.V[tTier], tFluids, tInputs); + + if (tRecipe == null || !tRecipe.isRecipeInputEqual(true, tFluids, tInputs)) + return false; + + if (mLockedToSingleRecipe) { + mSingleRecipeCheck = tSingleRecipeCheckBuilder.setAfter().setRecipe(tRecipe).build(); + } + } this.mEfficiency = (10000 - (getIdealStatus() - getRepairStatus()) * 1000); this.mEfficiencyIncrease = 10000; |