aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/gregtech
diff options
context:
space:
mode:
authorD-Cysteine <54219287+D-Cysteine@users.noreply.github.com>2021-11-14 18:49:05 -0700
committerD-Cysteine <54219287+D-Cysteine@users.noreply.github.com>2021-11-14 20:57:55 -0700
commita73802762161188fbd8539caba4164e4024fdeb3 (patch)
tree1a43cb021f9051bf71ae770aef339552ae8ab8a9 /src/main/java/gregtech
parent985f240b0e62d43f617da66eab06808ab45f7a5d (diff)
downloadGT5-Unofficial-a73802762161188fbd8539caba4164e4024fdeb3.tar.gz
GT5-Unofficial-a73802762161188fbd8539caba4164e4024fdeb3.tar.bz2
GT5-Unofficial-a73802762161188fbd8539caba4164e4024fdeb3.zip
Add single recipe check
Diffstat (limited to 'src/main/java/gregtech')
-rw-r--r--src/main/java/gregtech/api/metatileentity/implementations/GT_MetaTileEntity_MultiBlockBase.java24
-rw-r--r--src/main/java/gregtech/api/util/GT_LanguageManager.java2
-rw-r--r--src/main/java/gregtech/api/util/GT_Single_Recipe_Check.java283
-rw-r--r--src/main/java/gregtech/common/tileentities/machines/multi/GT_MetaTileEntity_PyrolyseOven.java49
4 files changed, 349 insertions, 9 deletions
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..bcf205611e
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_Single_Recipe_Check.java
@@ -0,0 +1,283 @@
+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 {
+ private final GT_MetaTileEntity_MultiBlockBase multiBlockBase;
+ private final GT_Recipe recipe;
+ private final ImmutableMap<ItemId, Integer> itemCost;
+ private final ImmutableMap<Fluid, Integer> fluidCost;
+ private final int totalItemCost;
+ private final int totalFluidCost;
+
+ private 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;
+ }
+
+ private 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;
+ }
+
+ private 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
+ abstract static class ItemId {
+ /** This method copies NBT, as it is mutable. */
+ private 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! */
+ private static ItemId createNoCopy(ItemStack itemStack) {
+ return new AutoValue_GT_Single_Recipe_Check_ItemId(
+ itemStack.getItem(), itemStack.getItemDamage(), itemStack.getTagCompound());
+ }
+
+ abstract Item item();
+ abstract int metaData();
+
+ @Nullable
+ abstract NBTTagCompound nbt();
+ }
+}
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..428931e654 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,49 @@ 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 {
+ 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);
+ }
+
+ ItemStack[] tInputs = getCompactedInputs();
+ FluidStack[] tFluids = getCompactedFluids();
+
+ if (tInputs.length <= 0)
+ return false;
+
+ if (mLockedToSingleRecipe) {
+ tSingleRecipeCheckBuilder.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;