package gregtech.api.util;

import static gregtech.api.enums.GT_Values.D1;
import static gregtech.api.enums.GT_Values.D2;
import static gregtech.api.enums.GT_Values.E;
import static gregtech.api.enums.GT_Values.L;
import static gregtech.api.enums.GT_Values.W;
import static gregtech.api.enums.Mods.GTPlusPlus;
import static gregtech.api.enums.Mods.GregTech;
import static gregtech.api.enums.Mods.NEICustomDiagrams;
import static gregtech.api.enums.Mods.Railcraft;
import static gregtech.api.recipe.check.FindRecipeResult.*;
import static gregtech.api.util.GT_RecipeBuilder.handleRecipeCollision;
import static gregtech.api.util.GT_RecipeConstants.ADDITIVE_AMOUNT;
import static gregtech.api.util.GT_RecipeMapUtil.FIRST_FLUIDSTACK_INPUT;
import static gregtech.api.util.GT_RecipeMapUtil.FIRST_FLUIDSTACK_OUTPUT;
import static gregtech.api.util.GT_RecipeMapUtil.FIRST_FLUID_OUTPUT;
import static gregtech.api.util.GT_RecipeMapUtil.FIRST_ITEM_INPUT;
import static gregtech.api.util.GT_RecipeMapUtil.FIRST_ITEM_OR_FLUID_INPUT;
import static gregtech.api.util.GT_RecipeMapUtil.FIRST_ITEM_OR_FLUID_OUTPUT;
import static gregtech.api.util.GT_RecipeMapUtil.FIRST_ITEM_OUTPUT;
import static gregtech.api.util.GT_RecipeMapUtil.GT_RecipeTemplate;
import static gregtech.api.util.GT_RecipeMapUtil.SPECIAL_VALUE_ALIASES;
import static gregtech.api.util.GT_RecipeMapUtil.asTemplate;
import static gregtech.api.util.GT_RecipeMapUtil.buildOrEmpty;
import static gregtech.api.util.GT_Utility.formatNumbers;
import static gregtech.api.util.GT_Utility.isArrayEmptyOrNull;
import static gregtech.api.util.GT_Utility.isArrayOfLength;
import static net.minecraft.util.EnumChatFormatting.GRAY;
import static net.minecraft.util.StatCollector.translateToLocal;

import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.init.Blocks;
import net.minecraft.init.Items;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.tileentity.TileEntityFurnace;
import net.minecraft.util.EnumChatFormatting;
import net.minecraftforge.fluids.Fluid;
import net.minecraftforge.fluids.FluidContainerRegistry;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.IFluidContainerItem;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;

import com.google.common.collect.Iterables;
import com.gtnewhorizons.modularui.api.GlStateManager;
import com.gtnewhorizons.modularui.api.ModularUITextures;
import com.gtnewhorizons.modularui.api.drawable.FallbackableUITexture;
import com.gtnewhorizons.modularui.api.drawable.IDrawable;
import com.gtnewhorizons.modularui.api.drawable.UITexture;
import com.gtnewhorizons.modularui.api.forge.IItemHandlerModifiable;
import com.gtnewhorizons.modularui.api.math.Alignment;
import com.gtnewhorizons.modularui.api.math.Pos2d;
import com.gtnewhorizons.modularui.api.math.Size;
import com.gtnewhorizons.modularui.api.screen.ModularWindow;
import com.gtnewhorizons.modularui.common.widget.DrawableWidget;
import com.gtnewhorizons.modularui.common.widget.ProgressBar;
import com.gtnewhorizons.modularui.common.widget.SlotWidget;

import appeng.util.ReadableNumberConverter;
import codechicken.lib.gui.GuiDraw;
import codechicken.nei.PositionedStack;
import cpw.mods.fml.common.Loader;
import cpw.mods.fml.common.ModContainer;
import gnu.trove.map.TByteObjectMap;
import gnu.trove.map.hash.TByteObjectHashMap;
import gregtech.GT_Mod;
import gregtech.api.GregTech_API;
import gregtech.api.enums.ConfigCategories;
import gregtech.api.enums.Dyes;
import gregtech.api.enums.Element;
import gregtech.api.enums.GT_Values;
import gregtech.api.enums.ItemList;
import gregtech.api.enums.Materials;
import gregtech.api.enums.OrePrefixes;
import gregtech.api.enums.SteamVariant;
import gregtech.api.enums.SubTag;
import gregtech.api.gui.GT_GUIColorOverride;
import gregtech.api.gui.modularui.FallbackableSteamTexture;
import gregtech.api.gui.modularui.GT_UITextures;
import gregtech.api.gui.modularui.SteamTexture;
import gregtech.api.interfaces.IGT_RecipeMap;
import gregtech.api.interfaces.tileentity.IHasWorldObjectAndCoords;
import gregtech.api.objects.GT_ItemStack;
import gregtech.api.objects.ItemData;
import gregtech.api.objects.MaterialStack;
import gregtech.api.recipe.check.FindRecipeResult;
import gregtech.api.util.extensions.ArrayExt;
import gregtech.common.gui.modularui.UIHelper;
import gregtech.common.items.GT_FluidDisplayItem;
import gregtech.common.misc.spaceprojects.SpaceProjectManager;
import gregtech.common.misc.spaceprojects.interfaces.ISpaceProject;
import gregtech.common.power.EUPower;
import gregtech.common.power.Power;
import gregtech.common.power.UnspecifiedEUPower;
import gregtech.common.tileentities.machines.basic.GT_MetaTileEntity_Replicator;
import gregtech.nei.FusionSpecialValueFormatter;
import gregtech.nei.GT_NEI_DefaultHandler;
import gregtech.nei.HeatingCoilSpecialValueFormatter;
import gregtech.nei.INEISpecialInfoFormatter;
import gregtech.nei.NEIRecipeInfo;
import ic2.core.Ic2Items;
import mods.railcraft.common.blocks.aesthetics.cube.EnumCube;
import mods.railcraft.common.items.RailcraftToolItems;

/**
 * NEVER INCLUDE THIS FILE IN YOUR MOD!!!
 * <p/>
 * This File contains the functions used for Recipes. Please do not include this File AT ALL in your Moddownload as it
 * ruins compatibility This is just the Core of my Recipe System, if you just want to GET the Recipes I add, then you
 * can access this File. Do NOT add Recipes using the Constructors inside this Class, The GregTech_API File calls the
 * correct Functions for these Constructors.
 * <p/>
 * I know this File causes some Errors, because of missing Main Functions, but if you just need to compile Stuff, then
 * remove said erroreous Functions.
 */
public class GT_Recipe implements Comparable<GT_Recipe> {

    /**
     * If you want to change the Output, feel free to modify or even replace the whole ItemStack Array, for Inputs,
     * please add a new Recipe, because of the HashMaps.
     */
    public ItemStack[] mInputs, mOutputs;
    /**
     * If you want to change the Output, feel free to modify or even replace the whole ItemStack Array, for Inputs,
     * please add a new Recipe, because of the HashMaps.
     */
    public FluidStack[] mFluidInputs, mFluidOutputs;
    /**
     * If you changed the amount of Array-Items inside the Output Array then the length of this Array must be larger or
     * equal to the Output Array. A chance of 10000 equals 100%
     */
    public int[] mChances;
    /**
     * An Item that needs to be inside the Special Slot, like for example the Copy Slot inside the Printer. This is only
     * useful for Fake Recipes in NEI, since findRecipe() and containsInput() don't give a shit about this Field. Lists
     * are also possible.
     */
    public Object mSpecialItems;

    public int mDuration, mEUt, mSpecialValue;
    /**
     * Use this to just disable a specific Recipe, but the Configuration enables that already for every single Recipe.
     */
    public boolean mEnabled = true;
    /**
     * If this Recipe is hidden from NEI
     */
    public boolean mHidden = false;
    /**
     * If this Recipe is Fake and therefore doesn't get found by the findRecipe Function (It is still in the HashMaps,
     * so that containsInput does return T on those fake Inputs)
     */
    public boolean mFakeRecipe = false;
    /**
     * If this Recipe can be stored inside a Machine in order to make Recipe searching more Efficient by trying the
     * previously used Recipe first. In case you have a Recipe Map overriding things and returning one time use Recipes,
     * you have to set this to F.
     */
    public boolean mCanBeBuffered = true;
    /**
     * If this Recipe needs the Output Slots to be completely empty. Needed in case you have randomised Outputs
     */
    public boolean mNeedsEmptyOutput = false;
    /**
     * Used for describing recipes that do not fit the default recipe pattern (for example Large Boiler Fuels)
     */
    private String[] neiDesc = null;
    /**
     * Stores which mod added this recipe
     */
    public List<ModContainer> owners = new ArrayList<>();
    /**
     * Stores stack traces where this recipe was added
     */
    public List<List<StackTraceElement>> stackTraces = new ArrayList<>();

    private GT_Recipe(GT_Recipe aRecipe, boolean shallow) {
        mInputs = shallow ? aRecipe.mInputs : GT_Utility.copyItemArray(aRecipe.mInputs);
        mOutputs = shallow ? aRecipe.mOutputs : GT_Utility.copyItemArray(aRecipe.mOutputs);
        mSpecialItems = aRecipe.mSpecialItems;
        mChances = aRecipe.mChances;
        mFluidInputs = shallow ? aRecipe.mFluidInputs : GT_Utility.copyFluidArray(aRecipe.mFluidInputs);
        mFluidOutputs = shallow ? aRecipe.mFluidOutputs : GT_Utility.copyFluidArray(aRecipe.mFluidOutputs);
        mDuration = aRecipe.mDuration;
        mSpecialValue = aRecipe.mSpecialValue;
        mEUt = aRecipe.mEUt;
        mNeedsEmptyOutput = aRecipe.mNeedsEmptyOutput;
        mCanBeBuffered = aRecipe.mCanBeBuffered;
        mFakeRecipe = aRecipe.mFakeRecipe;
        mEnabled = aRecipe.mEnabled;
        mHidden = aRecipe.mHidden;
        owners = new ArrayList<>(aRecipe.owners);
        reloadOwner();
    }

    // only used for GT_RecipeBuilder. Should not be called otherwise
    GT_Recipe(ItemStack[] mInputs, ItemStack[] mOutputs, FluidStack[] mFluidInputs, FluidStack[] mFluidOutputs,
        int[] mChances, Object mSpecialItems, int mDuration, int mEUt, int mSpecialValue, boolean mEnabled,
        boolean mHidden, boolean mFakeRecipe, boolean mCanBeBuffered, boolean mNeedsEmptyOutput, String[] neiDesc) {
        this.mInputs = mInputs;
        this.mOutputs = mOutputs;
        this.mFluidInputs = mFluidInputs;
        this.mFluidOutputs = mFluidOutputs;
        this.mChances = mChances;
        this.mSpecialItems = mSpecialItems;
        this.mDuration = mDuration;
        this.mEUt = mEUt;
        this.mSpecialValue = mSpecialValue;
        this.mEnabled = mEnabled;
        this.mHidden = mHidden;
        this.mFakeRecipe = mFakeRecipe;
        this.mCanBeBuffered = mCanBeBuffered;
        this.mNeedsEmptyOutput = mNeedsEmptyOutput;
        this.neiDesc = neiDesc;

        reloadOwner();
    }

    public GT_Recipe(boolean aOptimize, ItemStack[] aInputs, ItemStack[] aOutputs, Object aSpecialItems, int[] aChances,
        FluidStack[] aFluidInputs, FluidStack[] aFluidOutputs, int aDuration, int aEUt, int aSpecialValue) {
        if (aInputs == null) aInputs = new ItemStack[0];
        if (aOutputs == null) aOutputs = new ItemStack[0];
        if (aFluidInputs == null) aFluidInputs = new FluidStack[0];
        if (aFluidOutputs == null) aFluidOutputs = new FluidStack[0];
        if (aChances == null) aChances = new int[aOutputs.length];
        if (aChances.length < aOutputs.length) aChances = Arrays.copyOf(aChances, aOutputs.length);

        aInputs = ArrayExt.withoutTrailingNulls(aInputs, ItemStack[]::new);
        aOutputs = ArrayExt.withoutTrailingNulls(aOutputs, ItemStack[]::new);
        aFluidInputs = ArrayExt.withoutNulls(aFluidInputs, FluidStack[]::new);
        aFluidOutputs = ArrayExt.withoutNulls(aFluidOutputs, FluidStack[]::new);

        GT_OreDictUnificator.setStackArray(true, aInputs);
        GT_OreDictUnificator.setStackArray(true, aOutputs);

        for (ItemStack tStack : aOutputs) GT_Utility.updateItemStack(tStack);

        for (int i = 0; i < aChances.length; i++) if (aChances[i] <= 0) aChances[i] = 10000;
        for (int i = 0; i < aFluidInputs.length; i++) aFluidInputs[i] = aFluidInputs[i].copy();
        for (int i = 0; i < aFluidOutputs.length; i++) aFluidOutputs[i] = aFluidOutputs[i].copy();

        for (ItemStack aInput : aInputs)
            if (aInput != null && Items.feather.getDamage(aInput) != W) for (int j = 0; j < aOutputs.length; j++) {
                if (GT_Utility.areStacksEqual(aInput, aOutputs[j]) && aChances[j] >= 10000) {
                    if (aInput.stackSize >= aOutputs[j].stackSize) {
                        aInput.stackSize -= aOutputs[j].stackSize;
                        aOutputs[j] = null;
                    } else {
                        aOutputs[j].stackSize -= aInput.stackSize;
                    }
                }
            }

        if (aOptimize && aDuration >= 32) {
            ArrayList<ItemStack> tList = new ArrayList<>();
            tList.addAll(Arrays.asList(aInputs));
            tList.addAll(Arrays.asList(aOutputs));
            for (int i = 0; i < tList.size(); i++) if (tList.get(i) == null) tList.remove(i--);

            for (byte i = (byte) Math.min(64, aDuration / 16); i > 1; i--) if (aDuration / i >= 16) {
                boolean temp = true;
                for (ItemStack stack : tList) if (stack.stackSize % i != 0) {
                    temp = false;
                    break;
                }
                if (temp) for (FluidStack aFluidInput : aFluidInputs) if (aFluidInput.amount % i != 0) {
                    temp = false;
                    break;
                }
                if (temp) for (FluidStack aFluidOutput : aFluidOutputs) if (aFluidOutput.amount % i != 0) {
                    temp = false;
                    break;
                }
                if (temp) {
                    for (ItemStack itemStack : tList) itemStack.stackSize /= i;
                    for (FluidStack aFluidInput : aFluidInputs) aFluidInput.amount /= i;
                    for (FluidStack aFluidOutput : aFluidOutputs) aFluidOutput.amount /= i;
                    aDuration /= i;
                }
            }
        }

        mInputs = aInputs;
        mOutputs = aOutputs;
        mSpecialItems = aSpecialItems;
        mChances = aChances;
        mFluidInputs = aFluidInputs;
        mFluidOutputs = aFluidOutputs;
        mDuration = aDuration;
        mSpecialValue = aSpecialValue;
        mEUt = aEUt;
        // checkCellBalance();
        reloadOwner();
    }

    public GT_Recipe(ItemStack aInput1, ItemStack aOutput1, int aFuelValue, int aType) {
        this(aInput1, aOutput1, null, null, null, aFuelValue, aType);
    }

    private static FluidStack[] tryGetFluidInputsFromCells(ItemStack aInput) {
        FluidStack tFluid = GT_Utility.getFluidForFilledItem(aInput, true);
        return tFluid == null ? null : new FluidStack[] { tFluid };
    }

    // aSpecialValue = EU per Liter! If there is no Liquid for this Object, then it gets multiplied with 1000!
    public GT_Recipe(ItemStack aInput1, ItemStack aOutput1, ItemStack aOutput2, ItemStack aOutput3, ItemStack aOutput4,
        int aSpecialValue, int aType) {
        this(
            true,
            new ItemStack[] { aInput1 },
            new ItemStack[] { aOutput1, aOutput2, aOutput3, aOutput4 },
            null,
            null,
            null,
            null,
            0,
            0,
            Math.max(1, aSpecialValue));

        if (mInputs.length > 0 && aSpecialValue > 0) {
            switch (aType) {
                // Diesel Generator
                case 0 -> {
                    GT_Recipe_Map.sDieselFuels.addRecipe(this);
                    GT_Recipe_Map.sLargeBoilerFakeFuels.addDieselRecipe(this);
                }
                // Gas Turbine
                case 1 -> GT_Recipe_Map.sTurbineFuels.addRecipe(this);

                // Thermal Generator
                case 2 -> GT_Recipe_Map.sHotFuels.addRecipe(this);

                // Plasma Generator
                case 4 -> GT_Recipe_Map.sPlasmaFuels.addRecipe(this);

                // Magic Generator
                case 5 -> GT_Recipe_Map.sMagicFuels.addRecipe(this);

                // Fluid Generator. Usually 3. Every wrong Type ends up in the Semifluid Generator
                default -> {
                    GT_Recipe_Map.sDenseLiquidFuels.addRecipe(this);
                    GT_Recipe_Map.sLargeBoilerFakeFuels.addDenseLiquidRecipe(this);
                }
            }
        }
    }

    public GT_Recipe(FluidStack aInput1, FluidStack aInput2, FluidStack aOutput1, int aDuration, int aEUt,
        int aSpecialValue) {
        this(
            true,
            null,
            null,
            null,
            null,
            new FluidStack[] { aInput1, aInput2 },
            new FluidStack[] { aOutput1 },
            Math.max(aDuration, 1),
            aEUt,
            Math.max(Math.min(aSpecialValue, 160000000), 0));
        if (mInputs.length > 1) {
            GT_Recipe_Map.sFusionRecipes.addRecipe(this);
        }
    }

    public GT_Recipe(ItemStack aInput1, ItemStack aOutput1, ItemStack aOutput2, int aDuration, int aEUt) {
        this(
            true,
            new ItemStack[] { aInput1 },
            new ItemStack[] { aOutput1, aOutput2 },
            null,
            null,
            null,
            null,
            aDuration,
            aEUt,
            0);
        if (mInputs.length > 0 && mOutputs[0] != null) {
            GT_Recipe_Map.sLatheRecipes.addRecipe(this);
        }
    }

    public GT_Recipe(ItemStack aInput1, int aCellAmount, ItemStack aOutput1, ItemStack aOutput2, ItemStack aOutput3,
        ItemStack aOutput4, int aDuration, int aEUt) {
        this(
            true,
            new ItemStack[] { aInput1,
                aCellAmount > 0 ? ItemList.Cell_Empty.get(Math.min(64, Math.max(1, aCellAmount))) : null },
            new ItemStack[] { aOutput1, aOutput2, aOutput3, aOutput4 },
            null,
            null,
            null,
            null,
            Math.max(aDuration, 1),
            Math.max(aEUt, 1),
            0);
        if (mInputs.length > 0 && mOutputs[0] != null) {
            GT_Recipe_Map.sDistillationRecipes.addRecipe(this);
        }
    }

    public GT_Recipe(ItemStack aInput1, int aInput2, ItemStack aOutput1, ItemStack aOutput2) {
        this(
            true,
            new ItemStack[] { aInput1,
                GT_ModHandler.getIC2Item(
                    "industrialTnt",
                    aInput2 > 0 ? Math.min(aInput2, 64) : 1,
                    new ItemStack(Blocks.tnt, aInput2 > 0 ? Math.min(aInput2, 64) : 1)) },
            new ItemStack[] { aOutput1, aOutput2 },
            null,
            null,
            null,
            null,
            20,
            30,
            0);
        if (mInputs.length > 0 && mOutputs[0] != null) {
            GT_Recipe_Map.sImplosionRecipes.addRecipe(this);
        }
    }

    public GT_Recipe(int aEUt, int aDuration, ItemStack aInput1, ItemStack aOutput1) {
        this(
            true,
            new ItemStack[] { aInput1, ItemList.Circuit_Integrated.getWithDamage(0, aInput1.stackSize) },
            new ItemStack[] { aOutput1 },
            null,
            null,
            null,
            null,
            Math.max(aDuration, 1),
            Math.max(aEUt, 1),
            0);
        if (mInputs.length > 0 && mOutputs[0] != null) {
            GT_Recipe_Map.sBenderRecipes.addRecipe(this);
        }
    }

    public GT_Recipe(ItemStack aInput1, ItemStack aInput2, int aEUt, int aDuration, ItemStack aOutput1) {
        this(
            true,
            aInput2 == null ? new ItemStack[] { aInput1 } : new ItemStack[] { aInput1, aInput2 },
            new ItemStack[] { aOutput1 },
            null,
            null,
            null,
            null,
            Math.max(aDuration, 1),
            Math.max(aEUt, 1),
            0);
        if (mInputs.length > 0 && mOutputs[0] != null) {
            GT_Recipe_Map.sAlloySmelterRecipes.addRecipe(this);
        }
    }

    public GT_Recipe(ItemStack aInput1, int aEUt, ItemStack aInput2, int aDuration, ItemStack aOutput1,
        ItemStack aOutput2) {
        this(
            true,
            aInput2 == null ? new ItemStack[] { aInput1 } : new ItemStack[] { aInput1, aInput2 },
            new ItemStack[] { aOutput1, aOutput2 },
            null,
            null,
            null,
            null,
            Math.max(aDuration, 1),
            Math.max(aEUt, 1),
            0);
        if (mInputs.length > 0 && mOutputs[0] != null) {
            GT_Recipe_Map.sCannerRecipes.addRecipe(this);
        }
    }

    public GT_Recipe(ItemStack aInput1, ItemStack aOutput1, int aDuration) {
        this(
            true,
            new ItemStack[] { aInput1 },
            new ItemStack[] { aOutput1 },
            null,
            null,
            null,
            null,
            Math.max(aDuration, 1),
            120,
            0);
        if (mInputs.length > 0 && mOutputs[0] != null) {
            GT_Recipe_Map.sVacuumRecipes.addRecipe(this);
        }
    }

    public GT_Recipe(ItemStack aInput1, ItemStack aOutput1, int aDuration, int aEUt, int VACUUM) {
        this(
            true,
            new ItemStack[] { aInput1 },
            new ItemStack[] { aOutput1 },
            null,
            null,
            null,
            null,
            Math.max(aDuration, 1),
            aEUt,
            0);
        if (mInputs.length > 0 && mOutputs[0] != null) {
            GT_Recipe_Map.sVacuumRecipes.addRecipe(this);
        }
    }

    public GT_Recipe(FluidStack aInput1, FluidStack aOutput1, int aDuration, int aEUt) {
        this(
            false,
            null,
            null,
            null,
            null,
            new FluidStack[] { aInput1 },
            new FluidStack[] { aOutput1 },
            Math.max(aDuration, 1),
            aEUt,
            0);
        if (mFluidInputs.length > 0 && mFluidOutputs[0] != null) {
            GT_Recipe_Map.sVacuumRecipes.addRecipe(this);
        }
    }

    // Dummy GT_Recipe maker...
    public GT_Recipe(ItemStack[] aInputs, ItemStack[] aOutputs, Object aSpecialItems, int[] aChances,
        FluidStack[] aFluidInputs, FluidStack[] aFluidOutputs, int aDuration, int aEUt, int aSpecialValue) {
        this(
            true,
            aInputs,
            aOutputs,
            aSpecialItems,
            aChances,
            aFluidInputs,
            aFluidOutputs,
            aDuration,
            aEUt,
            aSpecialValue);
    }

    public static void reInit() {
        GT_Log.out.println("GT_Mod: Re-Unificating Recipes.");
        for (GT_Recipe_Map tMapEntry : GT_Recipe_Map.sMappings) tMapEntry.reInit();
    }

    // -----
    // Old Constructors, do not use!
    // -----

    public ItemStack getRepresentativeInput(int aIndex) {
        if (aIndex < 0 || aIndex >= mInputs.length) return null;
        return GT_Utility.copyOrNull(mInputs[aIndex]);
    }

    public ItemStack getOutput(int aIndex) {
        if (aIndex < 0 || aIndex >= mOutputs.length) return null;
        return GT_Utility.copyOrNull(mOutputs[aIndex]);
    }

    public int getOutputChance(int aIndex) {
        if (mChances == null) return 10000;
        if (aIndex < 0 || aIndex >= mChances.length) return 10000;
        return mChances[aIndex];
    }

    public FluidStack getRepresentativeFluidInput(int aIndex) {
        if (aIndex < 0 || aIndex >= mFluidInputs.length || mFluidInputs[aIndex] == null) return null;
        return mFluidInputs[aIndex].copy();
    }

    public FluidStack getFluidOutput(int aIndex) {
        if (aIndex < 0 || aIndex >= mFluidOutputs.length || mFluidOutputs[aIndex] == null) return null;
        return mFluidOutputs[aIndex].copy();
    }

    public void checkCellBalance() {
        if (!D2 || mInputs.length < 1) return;

        int tInputAmount = GT_ModHandler.getCapsuleCellContainerCountMultipliedWithStackSize(mInputs);
        int tOutputAmount = GT_ModHandler.getCapsuleCellContainerCountMultipliedWithStackSize(mOutputs);

        if (tInputAmount < tOutputAmount) {
            if (!Materials.Tin.contains(mInputs)) {
                GT_Log.err.println("You get more Cells, than you put in? There must be something wrong.");
                new Exception().printStackTrace(GT_Log.err);
            }
        } else if (tInputAmount > tOutputAmount) {
            if (!Materials.Tin.contains(mOutputs)) {
                GT_Log.err.println("You get less Cells, than you put in? GT Machines usually don't destroy Cells.");
                new Exception().printStackTrace(GT_Log.err);
            }
        }
    }

    public GT_Recipe copy() {
        return new GT_Recipe(this, false);
    }

    public GT_Recipe copyShallow() {
        return new GT_Recipe(this, true);
    }

    public boolean isRecipeInputEqual(boolean aDecreaseStacksizeBySuccess, FluidStack[] aFluidInputs,
        ItemStack... aInputs) {
        return isRecipeInputEqual(aDecreaseStacksizeBySuccess, false, 1, aFluidInputs, aInputs);
    }

    // For non-multiplied recipe amount values
    public boolean isRecipeInputEqual(boolean aDecreaseStacksizeBySuccess, boolean aDontCheckStackSizes,
        FluidStack[] aFluidInputs, ItemStack... aInputs) {
        return isRecipeInputEqual(aDecreaseStacksizeBySuccess, aDontCheckStackSizes, 1, aFluidInputs, aInputs);
    }

    /**
     * Okay, did some code archeology to figure out what's going on here.
     *
     * <p>
     * This variable was added in <a
     * href=https://github.com/GTNewHorizons/GT5-Unofficial/commit/9959ab7443982a19ad329bca424ab515493432e9>this
     * commit,</a> in order to fix the issues mentioned in <a
     * href=https://github.com/GTNewHorizons/GT5-Unofficial/pull/183>the PR</a>.
     *
     * <p>
     * It looks like it controls checking NBT. At this point, since we are still using universal fluid cells which store
     * their fluids in NBT, it probably will not be safe to disable the NBT checks in the near future. Data sticks may
     * be another case. Anyway, we probably can't get rid of this without some significant changes to clean up recipe
     * inputs.
     */
    public static boolean GTppRecipeHelper;

    /**
     * WARNING: Do not call this method with both {@code aDecreaseStacksizeBySuccess} and {@code aDontCheckStackSizes}
     * set to {@code true}! You'll get weird behavior.
     */
    public boolean isRecipeInputEqual(boolean aDecreaseStacksizeBySuccess, boolean aDontCheckStackSizes,
        int amountMultiplier, FluidStack[] aFluidInputs, ItemStack... aInputs) {
        if (mInputs.length > 0 && aInputs == null) return false;
        if (mFluidInputs.length > 0 && aFluidInputs == null) return false;

        // We need to handle 0-size recipe inputs. These are for inputs that don't get consumed.
        boolean inputFound;
        int remainingCost;

        // Array tracking modified fluid amounts. For efficiency, we will lazily initialize this array.
        // We use Integer so that we can have null as the default value, meaning unchanged.
        Integer[] newFluidAmounts = null;
        if (aFluidInputs != null) {
            newFluidAmounts = new Integer[aFluidInputs.length];

            for (FluidStack recipeFluidCost : mFluidInputs) {
                if (recipeFluidCost != null) {
                    inputFound = false;
                    remainingCost = recipeFluidCost.amount * amountMultiplier;

                    for (int i = 0; i < aFluidInputs.length; i++) {
                        FluidStack providedFluid = aFluidInputs[i];
                        if (providedFluid != null && providedFluid.isFluidEqual(recipeFluidCost)) {
                            inputFound = true;
                            if (newFluidAmounts[i] == null) {
                                newFluidAmounts[i] = providedFluid.amount;
                            }

                            if (aDontCheckStackSizes || newFluidAmounts[i] >= remainingCost) {
                                newFluidAmounts[i] -= remainingCost;
                                remainingCost = 0;
                                break;
                            } else {
                                remainingCost -= newFluidAmounts[i];
                                newFluidAmounts[i] = 0;
                            }
                        }
                    }

                    if (remainingCost > 0 || !inputFound) {
                        // Cost not satisfied, or for non-consumed inputs, input not found.
                        return false;
                    }
                }
            }
        }

        // Array tracking modified item stack sizes. For efficiency, we will lazily initialize this array.
        // We use Integer so that we can have null as the default value, meaning unchanged.
        Integer[] newItemAmounts = null;
        if (aInputs != null) {
            newItemAmounts = new Integer[aInputs.length];

            for (ItemStack recipeItemCost : mInputs) {
                ItemStack unifiedItemCost = GT_OreDictUnificator.get_nocopy(true, recipeItemCost);
                if (unifiedItemCost != null) {
                    inputFound = false;
                    remainingCost = recipeItemCost.stackSize * amountMultiplier;

                    for (int i = 0; i < aInputs.length; i++) {
                        ItemStack providedItem = aInputs[i];
                        if (GT_OreDictUnificator.isInputStackEqual(providedItem, unifiedItemCost)) {
                            if (GTppRecipeHelper) { // Please see JavaDoc on GTppRecipeHelper for why this is here.
                                if (GT_Utility.areStacksEqual(providedItem, Ic2Items.FluidCell.copy(), true)
                                    || GT_Utility.areStacksEqual(providedItem, ItemList.Tool_DataStick.get(1L), true)
                                    || GT_Utility.areStacksEqual(providedItem, ItemList.Tool_DataOrb.get(1L), true)) {
                                    if (!GT_Utility.areStacksEqual(providedItem, recipeItemCost, false)) continue;
                                }
                            }

                            inputFound = true;
                            if (newItemAmounts[i] == null) {
                                newItemAmounts[i] = providedItem.stackSize;
                            }

                            if (aDontCheckStackSizes || newItemAmounts[i] >= remainingCost) {
                                newItemAmounts[i] -= remainingCost;
                                remainingCost = 0;
                                break;
                            } else {
                                remainingCost -= newItemAmounts[i];
                                newItemAmounts[i] = 0;
                            }
                        }
                    }

                    if (remainingCost > 0 || !inputFound) {
                        // Cost not satisfied, or for non-consumed inputs, input not found.
                        return false;
                    }
                }
            }
        }

        if (aDecreaseStacksizeBySuccess) {
            // Copy modified amounts into the input stacks.
            if (aFluidInputs != null) {
                for (int i = 0; i < aFluidInputs.length; i++) {
                    if (newFluidAmounts[i] != null) {
                        aFluidInputs[i].amount = newFluidAmounts[i];
                    }
                }
            }

            if (aInputs != null) {
                for (int i = 0; i < aInputs.length; i++) {
                    if (newItemAmounts[i] != null) {
                        aInputs[i].stackSize = newItemAmounts[i];
                    }
                }
            }
        }

        return true;
    }

    @Override
    public int compareTo(GT_Recipe recipe) {
        // first lowest tier recipes
        // then fastest
        // then with lowest special value
        // then dry recipes
        // then with fewer inputs
        if (this.mEUt != recipe.mEUt) {
            return this.mEUt - recipe.mEUt;
        } else if (this.mDuration != recipe.mDuration) {
            return this.mDuration - recipe.mDuration;
        } else if (this.mSpecialValue != recipe.mSpecialValue) {
            return this.mSpecialValue - recipe.mSpecialValue;
        } else if (this.mFluidInputs.length != recipe.mFluidInputs.length) {
            return this.mFluidInputs.length - recipe.mFluidInputs.length;
        } else if (this.mInputs.length != recipe.mInputs.length) {
            return this.mInputs.length - recipe.mInputs.length;
        }
        return 0;
    }

    public String[] getNeiDesc() {
        return neiDesc;
    }

    /**
     * Sets description shown on NEI. <br>
     * If you have a large number of recipes for the recipemap, this is not efficient memory wise, so use
     * {@link GT_Recipe_Map#setNEISpecialInfoFormatter} instead.
     */
    protected void setNeiDesc(String... neiDesc) {
        this.neiDesc = neiDesc;
    }

    /**
     * Use {@link GT_Recipe_Map#getItemInputPositions} or {@link GT_Recipe_Map#getSpecialItemPosition} or
     * {@link GT_Recipe_Map#getFluidInputPositions} instead
     */
    @SuppressWarnings("DeprecatedIsStillUsed")
    @Deprecated
    public ArrayList<PositionedStack> getInputPositionedStacks() {
        return null;
    }

    /**
     * Use {@link GT_Recipe_Map#getItemOutputPositions} or {@link GT_Recipe_Map#getFluidOutputPositions} instead
     */
    @SuppressWarnings("DeprecatedIsStillUsed")
    @Deprecated
    public ArrayList<PositionedStack> getOutputPositionedStacks() {
        return null;
    }

    public void reloadOwner() {
        setOwner(
            Loader.instance()
                .activeModContainer());

        final List<String> excludedClasses = Arrays.asList(
            "java.lang.Thread",
            "gregtech.api.util.GT_Recipe",
            "gregtech.api.util.GT_RecipeBuilder",
            "gregtech.api.util.GT_Recipe$GT_Recipe_Map",
            "gregtech.common.GT_RecipeAdder");
        if (GT_Mod.gregtechproxy.mNEIRecipeOwnerStackTrace) {
            List<StackTraceElement> toAdd = new ArrayList<>();
            for (StackTraceElement stackTrace : Thread.currentThread()
                .getStackTrace()) {
                if (excludedClasses.stream()
                    .noneMatch(
                        c -> stackTrace.getClassName()
                            .equals(c))) {
                    toAdd.add(stackTrace);
                }
            }
            stackTraces.add(toAdd);
        }
    }

    public void setOwner(ModContainer newOwner) {
        ModContainer oldOwner = owners.size() > 0 ? this.owners.get(owners.size() - 1) : null;
        if (newOwner != null && newOwner != oldOwner) {
            owners.add(newOwner);
        }
    }

    /**
     * Use in case {@link Loader#activeModContainer()} isn't helpful
     */
    public void setOwner(String modId) {
        for (ModContainer mod : Loader.instance()
            .getModList()) {
            if (mod.getModId()
                .equals(modId)) {
                setOwner(mod);
                return;
            }
        }
    }

    public GT_Recipe setInputs(ItemStack... aInputs) {
        // TODO determine if we need this without trailing nulls call
        this.mInputs = ArrayExt.withoutTrailingNulls(aInputs, ItemStack[]::new);
        return this;
    }

    public GT_Recipe setOutputs(ItemStack... aOutputs) {
        this.mOutputs = ArrayExt.withoutTrailingNulls(aOutputs, ItemStack[]::new);
        return this;
    }

    public GT_Recipe setFluidInputs(FluidStack... aInputs) {
        this.mFluidInputs = ArrayExt.withoutTrailingNulls(aInputs, FluidStack[]::new);
        return this;
    }

    public GT_Recipe setFluidOutputs(FluidStack... aOutputs) {
        this.mFluidOutputs = ArrayExt.withoutTrailingNulls(aOutputs, FluidStack[]::new);
        return this;
    }

    public GT_Recipe setDuration(int aDuration) {
        this.mDuration = aDuration;
        return this;
    }

    public GT_Recipe setEUt(int aEUt) {
        this.mEUt = aEUt;
        return this;
    }

    public static class GT_Recipe_AssemblyLine {

        public static final ArrayList<GT_Recipe_AssemblyLine> sAssemblylineRecipes = new ArrayList<>();

        static {
            if (!Boolean.getBoolean("com.gtnh.gt5u.ignore-invalid-assline-recipe"))
                GregTech_API.sFirstWorldTick.add(GT_Recipe_AssemblyLine::checkInvalidRecipes);
            else GT_Log.out.println("NOT CHECKING INVALID ASSLINE RECIPE.");
        }

        private static void checkInvalidRecipes() {
            int invalidCount = 0;
            GT_Log.out.println("Started assline validation");
            for (GT_Recipe_AssemblyLine recipe : sAssemblylineRecipes) {
                if (recipe.getPersistentHash() == 0) {
                    invalidCount++;
                    GT_Log.err.printf("Invalid recipe: %s%n", recipe);
                }
            }
            if (invalidCount > 0) throw new RuntimeException(
                "There are " + invalidCount + " invalid assembly line recipe(s)! Check GregTech.log for details!");
        }

        public ItemStack mResearchItem;
        public int mResearchTime;
        public ItemStack[] mInputs;
        public FluidStack[] mFluidInputs;
        public ItemStack mOutput;
        public int mDuration;
        public int mEUt;
        public ItemStack[][] mOreDictAlt;
        private int mPersistentHash;

        /**
         * THIS CONSTRUCTOR DOES SET THE PERSISTENT HASH.
         * <p>
         * if you set one yourself, it will give you one of the RunetimeExceptions!
         */
        public GT_Recipe_AssemblyLine(ItemStack aResearchItem, int aResearchTime, ItemStack[] aInputs,
            FluidStack[] aFluidInputs, ItemStack aOutput, int aDuration, int aEUt) {
            this(
                aResearchItem,
                aResearchTime,
                aInputs,
                aFluidInputs,
                aOutput,
                aDuration,
                aEUt,
                new ItemStack[aInputs.length][]);
            int tPersistentHash = 1;
            for (ItemStack tInput : aInputs)
                tPersistentHash = tPersistentHash * 31 + GT_Utility.persistentHash(tInput, true, false);
            tPersistentHash = tPersistentHash * 31 + GT_Utility.persistentHash(aResearchItem, true, false);
            tPersistentHash = tPersistentHash * 31 + GT_Utility.persistentHash(aOutput, true, false);
            for (FluidStack tFluidInput : aFluidInputs)
                tPersistentHash = tPersistentHash * 31 + GT_Utility.persistentHash(tFluidInput, true, false);
            tPersistentHash = tPersistentHash * 31 + aResearchTime;
            tPersistentHash = tPersistentHash * 31 + aDuration;
            tPersistentHash = tPersistentHash * 31 + aEUt;
            setPersistentHash(tPersistentHash);
        }

        /**
         * THIS CONSTRUCTOR DOES <b>NOT</b> SET THE PERSISTENT HASH.
         * <p>
         * if you don't set one yourself, it will break a lot of stuff!
         */
        public GT_Recipe_AssemblyLine(ItemStack aResearchItem, int aResearchTime, ItemStack[] aInputs,
            FluidStack[] aFluidInputs, ItemStack aOutput, int aDuration, int aEUt, ItemStack[][] aAlt) {
            mResearchItem = aResearchItem;
            mResearchTime = aResearchTime;
            mInputs = aInputs;
            mFluidInputs = aFluidInputs;
            mOutput = aOutput;
            mDuration = aDuration;
            mEUt = aEUt;
            mOreDictAlt = aAlt;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            GT_ItemStack[] thisInputs = new GT_ItemStack[this.mInputs.length];
            int totalInputStackSize = 0;
            for (int i = 0; i < this.mInputs.length; i++) {
                thisInputs[i] = new GT_ItemStack(this.mInputs[i]);
                totalInputStackSize += thisInputs[i].mStackSize;
            }
            int inputHash = Arrays.deepHashCode(thisInputs);
            int inputFluidHash = Arrays.deepHashCode(this.mFluidInputs);
            GT_ItemStack thisOutput = new GT_ItemStack(mOutput);
            GT_ItemStack thisResearch = new GT_ItemStack(mResearchItem);
            int miscRecipeDataHash = Arrays.deepHashCode(
                new Object[] { totalInputStackSize, mDuration, mEUt, thisOutput, thisResearch, mResearchTime });
            result = prime * result + inputFluidHash;
            result = prime * result + inputHash;
            result = prime * result + miscRecipeDataHash;
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (!(obj instanceof GT_Recipe_AssemblyLine other)) {
                return false;
            }
            if (this.mInputs.length != other.mInputs.length) {
                return false;
            }
            if (this.mFluidInputs.length != other.mFluidInputs.length) {
                return false;
            }
            // Check Outputs Match
            GT_ItemStack output1 = new GT_ItemStack(this.mOutput);
            GT_ItemStack output2 = new GT_ItemStack(other.mOutput);
            if (!output1.equals(output2)) {
                return false;
            }
            // Check Scanned Item Match
            GT_ItemStack scan1 = new GT_ItemStack(this.mResearchItem);
            GT_ItemStack scan2 = new GT_ItemStack(other.mResearchItem);
            if (!scan1.equals(scan2)) {
                return false;
            }
            // Check Items Match
            GT_ItemStack[] thisInputs = new GT_ItemStack[this.mInputs.length];
            GT_ItemStack[] otherInputs = new GT_ItemStack[other.mInputs.length];
            for (int i = 0; i < thisInputs.length; i++) {
                thisInputs[i] = new GT_ItemStack(this.mInputs[i]);
                otherInputs[i] = new GT_ItemStack(other.mInputs[i]);
            }
            for (int i = 0; i < thisInputs.length; i++) {
                if (!thisInputs[i].equals(otherInputs[i]) || thisInputs[i].mStackSize != otherInputs[i].mStackSize) {
                    return false;
                }
            }
            // Check Fluids Match
            for (int i = 0; i < this.mFluidInputs.length; i++) {
                if (!this.mFluidInputs[i].isFluidStackIdentical(other.mFluidInputs[i])) {
                    return false;
                }
            }

            return this.mDuration == other.mDuration && this.mEUt == other.mEUt
                && this.mResearchTime == other.mResearchTime;
        }

        public int getPersistentHash() {
            if (mPersistentHash == 0)
                GT_Log.err.println("Assline recipe persistent hash has not been set! Recipe: " + mOutput);
            return mPersistentHash;
        }

        @Override
        public String toString() {
            return "GT_Recipe_AssemblyLine{" + "mResearchItem="
                + mResearchItem
                + ", mResearchTime="
                + mResearchTime
                + ", mInputs="
                + Arrays.toString(mInputs)
                + ", mFluidInputs="
                + Arrays.toString(mFluidInputs)
                + ", mOutput="
                + mOutput
                + ", mDuration="
                + mDuration
                + ", mEUt="
                + mEUt
                + ", mOreDictAlt="
                + Arrays.toString(mOreDictAlt)
                + '}';
        }

        /**
         * @param aPersistentHash the persistent hash. it should reflect the exact input used to generate this recipe If
         *                        0 is passed in, the actual persistent hash will be automatically remapped to 1
         *                        instead.
         * @throws IllegalStateException if the persistent hash has been set already
         */
        public void setPersistentHash(int aPersistentHash) {
            if (this.mPersistentHash != 0) throw new IllegalStateException("Cannot set persistent hash twice!");
            if (aPersistentHash == 0) this.mPersistentHash = 1;
            else this.mPersistentHash = aPersistentHash;
        }
    }

    @SuppressWarnings("StaticInitializerReferencesSubClass")
    public static class GT_Recipe_Map implements IGT_RecipeMap {

        /**
         * Contains all Recipe Maps
         */
        public static final Collection<GT_Recipe_Map> sMappings = new ArrayList<>();
        /**
         * All recipe maps indexed by their {@link #mUniqueIdentifier}.
         */
        public static final Map<String, GT_Recipe_Map> sIndexedMappings = new HashMap<>();

        static final String TEXTURES_GUI_BASICMACHINES = "textures/gui/basicmachines";
        public static final GT_Recipe_Map sOreWasherRecipes = new GT_Recipe_Map(
            new HashSet<>(500),
            "gt.recipe.orewasher",
            null,
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "OreWasher"),
            1,
            3,
            1,
            1,
            1,
            E,
            1,
            E,
            true,
            true).setSlotOverlay(false, false, GT_UITextures.OVERLAY_SLOT_CRUSHED_ORE)
                .setSlotOverlay(false, true, GT_UITextures.OVERLAY_SLOT_DUST)
                .setRecipeConfigFile("orewasher", FIRST_ITEM_INPUT)
                .setProgressBar(GT_UITextures.PROGRESSBAR_BATH, ProgressBar.Direction.CIRCULAR_CW);
        public static final GT_Recipe_Map sThermalCentrifugeRecipes = new GT_Recipe_Map(
            new HashSet<>(1000),
            "gt.recipe.thermalcentrifuge",
            null,
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "ThermalCentrifuge"),
            1,
            3,
            1,
            0,
            2,
            E,
            1,
            E,
            true,
            true).setSlotOverlay(false, false, GT_UITextures.OVERLAY_SLOT_CRUSHED_ORE)
                .setSlotOverlay(false, true, GT_UITextures.OVERLAY_SLOT_DUST)
                .setRecipeConfigFile("thermalcentrifuge", FIRST_ITEM_INPUT)
                .setProgressBar(GT_UITextures.PROGRESSBAR_ARROW, ProgressBar.Direction.RIGHT);
        public static final GT_Recipe_Map sCompressorRecipes = new GT_Recipe_Map(
            new HashSet<>(750),
            "gt.recipe.compressor",
            null,
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Compressor"),
            1,
            1,
            1,
            0,
            1,
            E,
            1,
            E,
            true,
            true).setSlotOverlay(false, false, GT_UITextures.OVERLAY_SLOT_COMPRESSOR)
                .setRecipeConfigFile("compressor", FIRST_ITEM_INPUT)
                .setProgressBar(GT_UITextures.PROGRESSBAR_COMPRESS, ProgressBar.Direction.RIGHT)
                .setSlotOverlaySteam(false, GT_UITextures.OVERLAY_SLOT_COMPRESSOR_STEAM)
                .setProgressBarSteam(GT_UITextures.PROGRESSBAR_COMPRESS_STEAM);
        public static final GT_Recipe_Map sExtractorRecipes = new GT_Recipe_Map(
            new HashSet<>(250),
            "gt.recipe.extractor",
            null,
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Extractor"),
            1,
            1,
            1,
            0,
            1,
            E,
            1,
            E,
            true,
            true).setSlotOverlay(false, false, GT_UITextures.OVERLAY_SLOT_CENTRIFUGE)
                .setRecipeConfigFile("extractor", FIRST_ITEM_INPUT)
                .setProgressBar(GT_UITextures.PROGRESSBAR_EXTRACT, ProgressBar.Direction.RIGHT)
                .setSlotOverlaySteam(false, GT_UITextures.OVERLAY_SLOT_CENTRIFUGE_STEAM)
                .setProgressBarSteam(GT_UITextures.PROGRESSBAR_EXTRACT_STEAM);
        public static final GT_Recipe_Map sRecyclerRecipes = new GT_Recipe_Map_Recycler(
            new HashSet<>(0),
            "ic.recipe.recycler",
            null,
            "ic2.recycler",
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Recycler"),
            1,
            1,
            1,
            0,
            1,
            E,
            1,
            E,
            true,
            false).setSlotOverlay(false, false, GT_UITextures.OVERLAY_SLOT_RECYCLE)
                .setProgressBar(GT_UITextures.PROGRESSBAR_RECYCLE, ProgressBar.Direction.CIRCULAR_CW);
        public static final GT_Recipe_Map sFurnaceRecipes = new GT_Recipe_Map_Furnace(
            new HashSet<>(0),
            "mc.recipe.furnace",
            "Furnace",
            "smelting",
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "E_Furnace"),
            1,
            1,
            1,
            0,
            1,
            E,
            1,
            E,
            true,
            false).setSlotOverlay(false, false, GT_UITextures.OVERLAY_SLOT_FURNACE)
                .setProgressBar(GT_UITextures.PROGRESSBAR_ARROW, ProgressBar.Direction.RIGHT)
                .setSlotOverlaySteam(false, GT_UITextures.OVERLAY_SLOT_FURNACE_STEAM)
                .setProgressBarSteam(GT_UITextures.PROGRESSBAR_ARROW_STEAM);
        public static final GT_Recipe_Map sMicrowaveRecipes = new GT_Recipe_Map_Microwave(
            new HashSet<>(0),
            "gt.recipe.microwave",
            null,
            "smelting",
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "E_Furnace"),
            1,
            1,
            1,
            0,
            1,
            E,
            1,
            E,
            true,
            false).setSlotOverlay(false, false, GT_UITextures.OVERLAY_SLOT_FURNACE)
                .setProgressBar(GT_UITextures.PROGRESSBAR_ARROW, ProgressBar.Direction.RIGHT);

        public static final GT_Recipe_Map sScannerFakeRecipes = new GT_Recipe_Map(
            new HashSet<>(300),
            "gt.recipe.scanner",
            null,
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Scanner"),
            1,
            1,
            1,
            0,
            1,
            E,
            1,
            E,
            true,
            true).setSlotOverlay(false, false, GT_UITextures.OVERLAY_SLOT_MICROSCOPE)
                .setSlotOverlay(false, false, true, true, GT_UITextures.OVERLAY_SLOT_DATA_ORB)
                .setProgressBar(GT_UITextures.PROGRESSBAR_ARROW, ProgressBar.Direction.RIGHT);
        public static final GT_Recipe_Map sRockBreakerFakeRecipes = new GT_Recipe_Map(
            new HashSet<>(200),
            "gt.recipe.rockbreaker",
            null,
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "RockBreaker"),
            2,
            1,
            0,
            0,
            1,
            E,
            1,
            E,
            true,
            true).setSlotOverlay(false, false, GT_UITextures.OVERLAY_SLOT_DUST)
                .setSlotOverlay(false, true, GT_UITextures.OVERLAY_SLOT_CRUSHED_ORE)
                .setProgressBar(GT_UITextures.PROGRESSBAR_MACERATE, ProgressBar.Direction.RIGHT);
        @Deprecated
        public static final GT_Recipe_Map sByProductList = new GT_Recipe_Map(
            new HashSet<>(1000),
            "gt.recipe.byproductlist",
            "Ore Byproduct List",
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Default"),
            1,
            6,
            1,
            0,
            1,
            E,
            1,
            E,
            true,
            false).setProgressBar(GT_UITextures.PROGRESSBAR_ARROW, ProgressBar.Direction.RIGHT);
        public static final GT_Recipe_Map sReplicatorFakeRecipes = new ReplicatorFakeMap(
            new HashSet<>(100),
            "gt.recipe.replicator",
            null,
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Replicator"),
            0,
            1,
            0,
            1,
            1,
            E,
            1,
            E,
            true,
            true).setSlotOverlay(false, false, GT_UITextures.OVERLAY_SLOT_CANISTER)
                .setSlotOverlay(true, false, GT_UITextures.OVERLAY_SLOT_UUM)
                .setSlotOverlay(false, false, true, true, GT_UITextures.OVERLAY_SLOT_DATA_ORB)
                .setProgressBar(GT_UITextures.PROGRESSBAR_ARROW, ProgressBar.Direction.RIGHT);
        public static final GT_Recipe_Map sAssemblylineVisualRecipes = new GT_Recipe_Map_AssemblyLineFake(
            new HashSet<>(110),
            "gt.recipe.fakeAssemblylineProcess",
            "Assemblyline Process",
            null,
            GregTech.getResourcePath("textures", "gui", "FakeAssemblyline"),
            16,
            1,
            1,
            0,
            1,
            E,
            1,
            E,
            true,
            true).setSlotOverlay(false, false, true, true, GT_UITextures.OVERLAY_SLOT_DATA_ORB)
                .setUsualFluidInputCount(4)
                .setDisableOptimize(true);
        /**
         * Usually, but not always, you should use {@link GT_RecipeConstants#UniversalArcFurnace} instead.
         */
        public static final GT_Recipe_Map sPlasmaArcFurnaceRecipes = new GT_Recipe_Map(
            new HashSet<>(20000),
            "gt.recipe.plasmaarcfurnace",
            null,
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "PlasmaArcFurnace"),
            1,
            9,
            1,
            1,
            1,
            E,
            1,
            E,
            true,
            true).setProgressBar(GT_UITextures.PROGRESSBAR_ARROW, ProgressBar.Direction.RIGHT)
                .setRecipeConfigFile("arcfurnace", FIRST_ITEM_INPUT);
        /**
         * Usually, but not always, you should use {@link GT_RecipeConstants#UniversalArcFurnace} instead.
         */
        public static final GT_Recipe_Map sArcFurnaceRecipes = new GT_Recipe_Map(
            new HashSet<>(20000),
            "gt.recipe.arcfurnace",
            null,
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "ArcFurnace"),
            1,
            9,
            1,
            1,
            3,
            E,
            1,
            E,
            true,
            true).setProgressBar(GT_UITextures.PROGRESSBAR_ARROW, ProgressBar.Direction.RIGHT)
                .setRecipeConfigFile("arcfurnace", FIRST_ITEM_INPUT);
        public static final GT_Recipe_Map sPrinterRecipes = new GT_Recipe_Map_Printer(
            new HashSet<>(5),
            "gt.recipe.printer",
            null,
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Printer"),
            1,
            1,
            1,
            1,
            1,
            E,
            1,
            E,
            true,
            true).setSlotOverlay(false, false, GT_UITextures.OVERLAY_SLOT_PAGE_BLANK)
                .setSlotOverlay(false, true, GT_UITextures.OVERLAY_SLOT_PAGE_PRINTED)
                .setSlotOverlay(false, false, true, true, GT_UITextures.OVERLAY_SLOT_DATA_STICK)
                .setRecipeConfigFile("printer", FIRST_ITEM_INPUT)
                .setProgressBar(GT_UITextures.PROGRESSBAR_ARROW, ProgressBar.Direction.RIGHT);
        public static final GT_Recipe_Map sSifterRecipes = new GT_Recipe_Map(
            new HashSet<>(105),
            "gt.recipe.sifter",
            null,
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Sifter"),
            1,
            9,
            0,
            0,
            1,
            E,
            1,
            E,
            true,
            true).setProgressBar(GT_UITextures.PROGRESSBAR_SIFT, ProgressBar.Direction.DOWN)
                .setRecipeConfigFile("sifter", FIRST_ITEM_INPUT);
        public static final GT_Recipe_Map sPressRecipes = new GT_Recipe_Map_FormingPress(
            new HashSet<>(300),
            "gt.recipe.press",
            null,
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Press3"),
            6,
            1,
            2,
            0,
            1,
            E,
            1,
            E,
            true,
            true).setSlotOverlay(false, false, true, GT_UITextures.OVERLAY_SLOT_PRESS_1)
                .setSlotOverlay(false, false, false, GT_UITextures.OVERLAY_SLOT_PRESS_2)
                .setSlotOverlay(false, true, GT_UITextures.OVERLAY_SLOT_PRESS_3)
                .setRecipeConfigFile("press", FIRST_ITEM_OUTPUT)
                .setProgressBar(GT_UITextures.PROGRESSBAR_COMPRESS, ProgressBar.Direction.RIGHT);
        public static final GT_Recipe_Map sLaserEngraverRecipes = new GT_Recipe_Map(
            new HashSet<>(810),
            "gt.recipe.laserengraver",
            null,
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "LaserEngraver2"),
            4,
            4,
            0,
            0,
            1,
            E,
            1,
            E,
            true,
            true).setSlotOverlay(false, false, false, GT_UITextures.OVERLAY_SLOT_LENS)
                .setProgressBar(GT_UITextures.PROGRESSBAR_ARROW, ProgressBar.Direction.RIGHT)
                .setRecipeConfigFile("laserengraving", FIRST_ITEM_OUTPUT)
                .setUsualFluidInputCount(2)
                .setUsualFluidOutputCount(2);
        public static final GT_Recipe_Map sMixerRecipes = new GT_Recipe_Map(
            new HashSet<>(900),
            "gt.recipe.mixer",
            null,
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Mixer6"),
            9,
            4,
            1,
            0,
            1,
            E,
            1,
            E,
            true,
            true).setSlotOverlay(false, false, GT_UITextures.OVERLAY_SLOT_DUST)
                .setSlotOverlay(false, true, GT_UITextures.OVERLAY_SLOT_DUST)
                .setRecipeConfigFile("mixer", FIRST_ITEM_OR_FLUID_OUTPUT)
                .setProgressBar(GT_UITextures.PROGRESSBAR_MIXER, ProgressBar.Direction.CIRCULAR_CW);
        public static final GT_Recipe_Map sAutoclaveRecipes = new GT_Recipe_Map(
            new HashSet<>(300),
            "gt.recipe.autoclave",
            null,
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Autoclave4"),
            2,
            4,
            1,
            1,
            1,
            E,
            1,
            E,
            true,
            true).setSlotOverlay(false, false, GT_UITextures.OVERLAY_SLOT_DUST)
                .setSlotOverlay(false, true, true, GT_UITextures.OVERLAY_SLOT_GEM)
                .setSlotOverlay(false, true, false, GT_UITextures.OVERLAY_SLOT_DUST)
                .setRecipeConfigFile("autoclave", FIRST_ITEM_INPUT)
                .setProgressBar(GT_UITextures.PROGRESSBAR_ARROW, ProgressBar.Direction.RIGHT);
        public static final GT_Recipe_Map sElectroMagneticSeparatorRecipes = new GT_Recipe_Map(
            new HashSet<>(50),
            "gt.recipe.electromagneticseparator",
            null,
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "ElectromagneticSeparator"),
            1,
            3,
            1,
            0,
            1,
            E,
            1,
            E,
            true,
            true).setSlotOverlay(false, false, GT_UITextures.OVERLAY_SLOT_CRUSHED_ORE)
                .setSlotOverlay(false, true, GT_UITextures.OVERLAY_SLOT_DUST)
                .setRecipeConfigFile("electromagneticseparator", FIRST_ITEM_INPUT)
                .setProgressBar(GT_UITextures.PROGRESSBAR_MAGNET, ProgressBar.Direction.RIGHT);
        public static final GT_Recipe_Map sPolarizerRecipes = new GT_Recipe_Map(
            new HashSet<>(300),
            "gt.recipe.polarizer",
            null,
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Polarizer"),
            1,
            1,
            1,
            0,
            1,
            E,
            1,
            E,
            true,
            true).setProgressBar(GT_UITextures.PROGRESSBAR_MAGNET, ProgressBar.Direction.RIGHT)
                .setRecipeConfigFile("polarizer", FIRST_ITEM_INPUT);
        public static final GT_Recipe_Map sMaceratorRecipes = new GT_Recipe_Map_Macerator(
            new HashSet<>(16600),
            "gt.recipe.macerator",
            null,
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Macerator4"),
            1,
            4,
            1,
            0,
            1,
            E,
            1,
            E,
            true,
            true).setSlotOverlay(false, false, GT_UITextures.OVERLAY_SLOT_CRUSHED_ORE)
                .setSlotOverlay(false, true, GT_UITextures.OVERLAY_SLOT_DUST)
                .setProgressBar(GT_UITextures.PROGRESSBAR_MACERATE, ProgressBar.Direction.RIGHT)
                .setRecipeConfigFile("pulveriser", FIRST_ITEM_INPUT)
                .setSlotOverlaySteam(false, GT_UITextures.OVERLAY_SLOT_CRUSHED_ORE_STEAM)
                .setSlotOverlaySteam(true, GT_UITextures.OVERLAY_SLOT_DUST_STEAM)
                .setProgressBarSteam(GT_UITextures.PROGRESSBAR_MACERATE_STEAM);
        public static final GT_Recipe_Map sChemicalBathRecipes = new GT_Recipe_Map(
            new HashSet<>(2550),
            "gt.recipe.chemicalbath",
            null,
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "ChemicalBath"),
            1,
            3,
            1,
            1,
            1,
            E,
            1,
            E,
            true,
            true).setProgressBar(GT_UITextures.PROGRESSBAR_BATH, ProgressBar.Direction.CIRCULAR_CW)
                .setRecipeConfigFile("chemicalbath", FIRST_ITEM_INPUT);
        public static final GT_Recipe_Map sFluidCannerRecipes = new GT_Recipe_Map_FluidCanner(
            new HashSet<>(2100),
            "gt.recipe.fluidcanner",
            null,
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "FluidCanner"),
            1,
            1,
            1,
            0,
            1,
            E,
            1,
            E,
            true,
            true).setSlotOverlay(false, false, GT_UITextures.OVERLAY_SLOT_CANISTER)
                .setSlotOverlay(false, true, GT_UITextures.OVERLAY_SLOT_CANISTER)
                .setRecipeConfigFile("canning", FIRST_ITEM_INPUT)
                .setProgressBar(GT_UITextures.PROGRESSBAR_CANNER, ProgressBar.Direction.RIGHT);
        public static final GT_Recipe_Map sBrewingRecipes = new GT_Recipe_Map(
            new HashSet<>(450),
            "gt.recipe.brewer",
            "Brewing Machine",
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "PotionBrewer"),
            1,
            0,
            1,
            1,
            1,
            E,
            1,
            E,
            true,
            true).setSlotOverlay(false, false, GT_UITextures.OVERLAY_SLOT_CAULDRON)
                .setRecipeConfigFile("brewing", FIRST_FLUIDSTACK_OUTPUT)
                .setProgressBar(GT_UITextures.PROGRESSBAR_ARROW_MULTIPLE, ProgressBar.Direction.RIGHT);
        public static final GT_Recipe_Map sFluidHeaterRecipes = new GT_Recipe_Map(
            new HashSet<>(10),
            "gt.recipe.fluidheater",
            null,
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "FluidHeater"),
            1,
            0,
            0,
            0,
            1,
            E,
            1,
            E,
            true,
            true).setSlotOverlay(true, false, GT_UITextures.OVERLAY_SLOT_HEATER_1)
                .setSlotOverlay(true, true, GT_UITextures.OVERLAY_SLOT_HEATER_2)
                .setRecipeConfigFile("fluidheater", FIRST_FLUIDSTACK_OUTPUT)
                .setProgressBar(GT_UITextures.PROGRESSBAR_ARROW_MULTIPLE, ProgressBar.Direction.RIGHT);
        public static final GT_Recipe_Map sDistilleryRecipes = new GT_Recipe_Map(
            new HashSet<>(400),
            "gt.recipe.distillery",
            null,
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Distillery"),
            1,
            1,
            1,
            1,
            1,
            E,
            1,
            E,
            true,
            true).setSlotOverlay(true, false, GT_UITextures.OVERLAY_SLOT_BEAKER_1)
                .setSlotOverlay(true, true, GT_UITextures.OVERLAY_SLOT_BEAKER_2)
                .setRecipeConfigFile("distillery", FIRST_FLUIDSTACK_OUTPUT)
                .setRecipeSpecialHandler(r -> {
                    int aInput = r.mFluidInputs[0].amount, aOutput = r.mFluidOutputs[0].amount, aDuration = r.mDuration;

                    // reduce the batch size if fluid amount is exceeding
                    int tScale = (Math.max(aInput, aOutput) + 999) / 1000;
                    if (tScale <= 0) tScale = 1;
                    if (tScale > 1) {
                        // trying to find whether there is a better factor
                        for (int i = tScale; i <= 5; i++) {
                            if (aInput % i == 0 && aDuration % i == 0) {
                                tScale = i;
                                break;
                            }
                        }
                        for (int i = tScale; i <= 5; i++) {
                            if (aInput % i == 0 && aDuration % i == 0 && aOutput % i == 0) {
                                tScale = i;
                                break;
                            }
                        }
                        aInput = (aInput + tScale - 1) / tScale;
                        aOutput = aOutput / tScale;
                        if (!isArrayEmptyOrNull(r.mOutputs)) {
                            ItemData tData = GT_OreDictUnificator.getItemData(r.mOutputs[0]);
                            if (tData != null && (tData.mPrefix == OrePrefixes.dust
                                || OrePrefixes.dust.mFamiliarPrefixes.contains(tData.mPrefix))) {
                                r.mOutputs[0] = GT_OreDictUnificator.getDust(
                                    tData.mMaterial.mMaterial,
                                    tData.mMaterial.mAmount * r.mOutputs[0].stackSize / tScale);
                            } else {
                                if (r.mOutputs[0].stackSize / tScale == 0) r.mOutputs[0] = GT_Values.NI;
                                else r.mOutputs[0] = GT_Utility
                                    .copyAmount(r.mOutputs[0].stackSize / tScale, r.mOutputs[0]);
                            }
                        }
                        aDuration = (aDuration + tScale - 1) / tScale;
                        r.mFluidInputs[0] = GT_Utility.copyAmount(aInput, r.mFluidInputs[0]);
                        r.mFluidOutputs[0] = GT_Utility.copyAmount(aOutput, r.mFluidOutputs[0]);
                        r.mDuration = aDuration;
                    }
                })
                .setProgressBar(GT_UITextures.PROGRESSBAR_ARROW_MULTIPLE, ProgressBar.Direction.RIGHT);
        public static final GT_Recipe_Map sFermentingRecipes = new GT_Recipe_Map(
            new HashSet<>(50),
            "gt.recipe.fermenter",
            null,
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Fermenter"),
            0,
            0,
            0,
            1,
            1,
            E,
            1,
            E,
            true,
            true).setProgressBar(GT_UITextures.PROGRESSBAR_ARROW_MULTIPLE, ProgressBar.Direction.RIGHT)
                .setRecipeConfigFile("fermenting", FIRST_FLUIDSTACK_OUTPUT);
        public static final GT_Recipe_Map sFluidSolidficationRecipes = new GT_Recipe_Map(
            new HashSet<>(35000),
            "gt.recipe.fluidsolidifier",
            null,
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "FluidSolidifier"),
            1,
            1,
            1,
            1,
            1,
            E,
            1,
            E,
            true,
            true).setSlotOverlay(false, false, GT_UITextures.OVERLAY_SLOT_MOLD)
                .setRecipeConfigFile("fluidsolidifier", FIRST_ITEM_OUTPUT)
                .setRecipeSpecialHandler(r -> {
                    if (ArrayUtils.isNotEmpty(r.mFluidInputs)) {
                        if (Materials.PhasedGold.getMolten(1)
                            .isFluidEqual(r.mFluidInputs[0]))
                            r.mFluidInputs = new FluidStack[] {
                                Materials.VibrantAlloy.getMolten(r.mFluidInputs[0].amount) };
                        else if (Materials.PhasedIron.getMolten(1)
                            .isFluidEqual(r.mFluidInputs[0]))
                            r.mFluidInputs = new FluidStack[] {
                                Materials.PulsatingIron.getMolten(r.mFluidInputs[0].amount) };
                    }
                })
                .setProgressBar(GT_UITextures.PROGRESSBAR_ARROW, ProgressBar.Direction.RIGHT);
        public static final GT_Recipe_Map sFluidExtractionRecipes = new GT_Recipe_Map(
            new HashSet<>(15000),
            "gt.recipe.fluidextractor",
            null,
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "FluidExtractor"),
            1,
            1,
            1,
            0,
            1,
            E,
            1,
            E,
            true,
            true).setSlotOverlay(false, false, GT_UITextures.OVERLAY_SLOT_CENTRIFUGE)
                .setRecipeConfigFile("fluidextractor", FIRST_ITEM_INPUT)
                .setRecipeSpecialHandler(r -> {
                    if (ArrayUtils.isNotEmpty(r.mFluidInputs)) {
                        if (Materials.PhasedGold.getMolten(1)
                            .isFluidEqual(r.mFluidInputs[0]))
                            r.mFluidInputs = new FluidStack[] {
                                Materials.VibrantAlloy.getMolten(r.mFluidInputs[0].amount) };
                        else if (Materials.PhasedIron.getMolten(1)
                            .isFluidEqual(r.mFluidInputs[0]))
                            r.mFluidInputs = new FluidStack[] {
                                Materials.PulsatingIron.getMolten(r.mFluidInputs[0].amount) };
                    }
                })
                .setProgressBar(GT_UITextures.PROGRESSBAR_EXTRACT, ProgressBar.Direction.RIGHT);
        public static final GT_Recipe_Map sBoxinatorRecipes = new GT_Recipe_Map(
            new HashSet<>(2500),
            "gt.recipe.packager",
            null,
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Packager"),
            2,
            1,
            2,
            0,
            1,
            E,
            1,
            E,
            true,
            true).setSlotOverlay(false, false, false, GT_UITextures.OVERLAY_SLOT_BOX)
                .setRecipeConfigFile("boxing", FIRST_ITEM_OUTPUT)
                .setSlotOverlay(false, true, GT_UITextures.OVERLAY_SLOT_BOXED)
                .setProgressBar(GT_UITextures.PROGRESSBAR_ARROW, ProgressBar.Direction.RIGHT);
        public static final GT_Recipe_Map sUnboxinatorRecipes = new GT_Recipe_Map_Unboxinator(
            new HashSet<>(2500),
            "gt.recipe.unpackager",
            null,
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Unpackager"),
            1,
            2,
            1,
            0,
            1,
            E,
            1,
            E,
            true,
            true).setSlotOverlay(false, false, GT_UITextures.OVERLAY_SLOT_BOXED)
                .setRecipeConfigFile("unboxing", FIRST_ITEM_OUTPUT)
                .setProgressBar(GT_UITextures.PROGRESSBAR_ARROW, ProgressBar.Direction.RIGHT);
        /**
         * Usually, but not always, you should use {@link GT_RecipeConstants#Fusion} instead.
         */
        public static final GT_Recipe_Map sFusionRecipes = new GT_Recipe_Map_FluidOnly(
            new HashSet<>(50),
            "gt.recipe.fusionreactor",
            "Fusion Reactor",
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "FusionReactor"),
            0,
            0,
            0,
            2,
            1,
            "Start: ",
            1,
            " EU",
            true,
            true).setProgressBar(GT_UITextures.PROGRESSBAR_ARROW, ProgressBar.Direction.RIGHT)
                .useComparatorForNEI(true)
                .setUsualFluidInputCount(2)
                .setRecipeConfigFile("fusion", FIRST_FLUID_OUTPUT)
                .setDisableOptimize(true)
                .setNEISpecialInfoFormatter(FusionSpecialValueFormatter.INSTANCE);
        /**
         * Usually, but not always, you should use {@link GT_RecipeConstants#Fusion} instead.
         */
        public static final GT_Recipe_Map sComplexFusionRecipes = new GT_Recipe_Map_ComplexFusion(
            new HashSet<>(50),
            "gt.recipe.complexfusionreactor",
            "Complex Fusion Reactor",
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "ComplexFusionReactor"),
            3,
            0,
            0,
            2,
            1,
            "Start: ",
            1,
            " EU",
            true,
            true).setProgressBar(GT_UITextures.PROGRESSBAR_ARROW, ProgressBar.Direction.RIGHT)
                .setUsualFluidInputCount(16)
                .setUsualFluidOutputCount(16)
                .setNEITransferRect(new Rectangle(79, 34, 18, 18))
                .setLogoPos(80, 61)
                .setNEISpecialInfoFormatter(FusionSpecialValueFormatter.INSTANCE)
                .setDisableOptimize(true);
        public static final GT_Recipe_Map sCentrifugeRecipes = new GT_Recipe_Map(
            new HashSet<>(1200),
            "gt.recipe.centrifuge",
            null,
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Centrifuge"),
            2,
            6,
            0,
            0,
            1,
            E,
            1,
            E,
            true,
            true).setSlotOverlay(false, false, true, GT_UITextures.OVERLAY_SLOT_CENTRIFUGE)
                .setRecipeConfigFile("centrifuge", FIRST_ITEM_OR_FLUID_INPUT)
                .setSlotOverlay(false, false, false, GT_UITextures.OVERLAY_SLOT_CANISTER)
                .setSlotOverlay(true, false, GT_UITextures.OVERLAY_SLOT_CENTRIFUGE_FLUID)
                .setProgressBar(GT_UITextures.PROGRESSBAR_EXTRACT, ProgressBar.Direction.RIGHT);
        public static final GT_Recipe_Map sElectrolyzerRecipes = new GT_Recipe_Map(
            new HashSet<>(300),
            "gt.recipe.electrolyzer",
            null,
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Electrolyzer"),
            2,
            6,
            0,
            0,
            1,
            E,
            1,
            E,
            true,
            true).setSlotOverlay(false, false, true, GT_UITextures.OVERLAY_SLOT_CHARGER)
                .setRecipeConfigFile("electrolyzer", FIRST_ITEM_OR_FLUID_INPUT)
                .setSlotOverlay(false, false, false, GT_UITextures.OVERLAY_SLOT_CANISTER)
                .setSlotOverlay(true, false, GT_UITextures.OVERLAY_SLOT_CHARGER_FLUID)
                .setProgressBar(GT_UITextures.PROGRESSBAR_EXTRACT, ProgressBar.Direction.RIGHT);
        /**
         * Use {@link GT_RecipeConstants#COIL_HEAT} as heat level.
         */
        public static final GT_Recipe_Map sBlastRecipes = new GT_Recipe_Map(
            new HashSet<>(800),
            "gt.recipe.blastfurnace",
            "Blast Furnace",
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Default"),
            6,
            6,
            1,
            0,
            1,
            "Heat Capacity: ",
            1,
            " K",
            false,
            true).setProgressBar(GT_UITextures.PROGRESSBAR_ARROW, ProgressBar.Direction.RIGHT)
                .setRecipeConfigFile("blastfurnace", FIRST_ITEM_INPUT)
                .setNEISpecialInfoFormatter(HeatingCoilSpecialValueFormatter.INSTANCE);
        /**
         * Use {@link GT_RecipeConstants#COIL_HEAT} as heat level.
         */
        public static final GT_Recipe_Map sPlasmaForgeRecipes = new GT_Recipe_Map_LargeNEI(
            new HashSet<>(20),
            "gt.recipe.plasmaforge",
            "DTPF",
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "PlasmaForge"),
            9,
            9,
            0,
            0,
            1,
            "Heat Capacity: ",
            1,
            " K",
            false,
            true).setProgressBar(GT_UITextures.PROGRESSBAR_ARROW, ProgressBar.Direction.RIGHT)
                .setUsualFluidInputCount(9)
                .setUsualFluidOutputCount(9)
                .setDisableOptimize(true)
                .setNEISpecialInfoFormatter(HeatingCoilSpecialValueFormatter.INSTANCE);

        public static final GT_Recipe_Map sTranscendentPlasmaMixerRecipes = new TranscendentPlasmaMixerRecipeMap(
            new HashSet<>(20),
            "gt.recipe.transcendentplasmamixerrecipes",
            "Transcendent Plasma Mixer",
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "PlasmaForge"),
            1,
            0,
            0,
            0,
            1,
            "",
            0,
            "",
            false,
            true).setDisableOptimize(true);

        public static class GT_FakeSpaceProjectRecipe extends GT_Recipe {

            public final String projectName;

            public GT_FakeSpaceProjectRecipe(boolean aOptimize, ItemStack[] aInputs, FluidStack[] aFluidInputs,
                int aDuration, int aEUt, int aSpecialValue, String projectName) {
                super(aOptimize, aInputs, null, null, null, aFluidInputs, null, aDuration, aEUt, aSpecialValue);
                this.projectName = projectName;
            }
        }

        public static final GT_Recipe_Map sFakeSpaceProjectRecipes = new GT_Recipe_Map(
            new HashSet<>(20),
            "gt.recipe.fakespaceprojects",
            "Space Projects",
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Default"),
            12,
            0,
            0,
            0,
            1,
            translateToLocal("gt.specialvalue.stages") + " ",
            1,
            "",
            false,
            true) {

            IDrawable projectTexture;

            @Override
            public ModularWindow.Builder createNEITemplate(IItemHandlerModifiable itemInputsInventory,
                IItemHandlerModifiable itemOutputsInventory, IItemHandlerModifiable specialSlotInventory,
                IItemHandlerModifiable fluidInputsInventory, IItemHandlerModifiable fluidOutputsInventory,
                Supplier<Float> progressSupplier, Pos2d windowOffset) {
                ModularWindow.Builder builder = super.createNEITemplate(
                    itemInputsInventory,
                    itemOutputsInventory,
                    specialSlotInventory,
                    fluidInputsInventory,
                    fluidOutputsInventory,
                    progressSupplier,
                    windowOffset);
                addRecipeSpecificDrawable(
                    builder,
                    windowOffset,
                    () -> projectTexture,
                    new Pos2d(124, 28),
                    new Size(18, 18));
                return builder;
            }

            @Override
            public List<Pos2d> getItemInputPositions(int itemInputCount) {
                return UIHelper.getGridPositions(itemInputCount, 16, 28, 3);
            }

            @Override
            public List<Pos2d> getFluidInputPositions(int fluidInputCount) {
                return UIHelper.getGridPositions(fluidInputCount, 88, 28, 1);
            }

            @Override
            protected List<String> handleNEIItemInputTooltip(List<String> currentTip,
                GT_NEI_DefaultHandler.FixedPositionedStack pStack) {
                super.handleNEIItemOutputTooltip(currentTip, pStack);
                if (pStack.item != null && pStack.item.getItem() instanceof GT_FluidDisplayItem) return currentTip;
                currentTip.add(GRAY + translateToLocal("Item Count: ") + formatNumbers(pStack.realStackSize));
                return currentTip;
            }

            @Override
            public void drawNEIOverlays(GT_NEI_DefaultHandler.CachedDefaultRecipe neiCachedRecipe) {
                for (PositionedStack stack : neiCachedRecipe.mInputs) {
                    if (stack instanceof GT_NEI_DefaultHandler.FixedPositionedStack && stack.item != null
                        && !(stack.item.getItem() instanceof GT_FluidDisplayItem)) {
                        int stackSize = ((GT_NEI_DefaultHandler.FixedPositionedStack) stack).realStackSize;
                        String displayString;
                        if (stack.item.stackSize > 9999) {
                            displayString = ReadableNumberConverter.INSTANCE.toWideReadableForm(stackSize);
                        } else {
                            displayString = String.valueOf(stackSize);
                        }
                        drawNEIOverlayText(displayString, stack, 0xffffff, 0.5f, true, Alignment.BottomRight);
                    }
                }
                if (neiCachedRecipe.mRecipe instanceof GT_FakeSpaceProjectRecipe) {
                    ISpaceProject project = SpaceProjectManager
                        .getProject(((GT_FakeSpaceProjectRecipe) neiCachedRecipe.mRecipe).projectName);
                    if (project != null) {
                        projectTexture = project.getTexture();
                        GuiDraw
                            .drawStringC(EnumChatFormatting.BOLD + project.getLocalizedName(), 85, 0, 0x404040, false);
                    }
                }
            }

            @Override
            public void addProgressBarUI(ModularWindow.Builder builder, Supplier<Float> progressSupplier,
                Pos2d windowOffset) {
                int bar1Width = 17;
                int bar2Width = 18;
                builder.widget(
                    new ProgressBar().setTexture(GT_UITextures.PROGRESSBAR_ASSEMBLY_LINE_1, 17)
                        .setDirection(ProgressBar.Direction.RIGHT)
                        .setProgress(() -> progressSupplier.get() * ((float) (bar1Width + bar2Width) / bar1Width))
                        .setSynced(false, false)
                        .setPos(new Pos2d(70, 28).add(windowOffset))
                        .setSize(bar1Width, 72));
                builder.widget(
                    new ProgressBar().setTexture(GT_UITextures.PROGRESSBAR_ASSEMBLY_LINE_2, 18)
                        .setDirection(ProgressBar.Direction.RIGHT)
                        .setProgress(
                            () -> (progressSupplier.get() - ((float) bar1Width / (bar1Width + bar2Width)))
                                * ((float) (bar1Width + bar2Width) / bar2Width))
                        .setSynced(false, false)
                        .setPos(new Pos2d(106, 28).add(windowOffset))
                        .setSize(bar2Width, 72));
            }
        }.useModularUI(true)
            .setRenderRealStackSizes(false)
            .setUsualFluidInputCount(4)
            .setNEIBackgroundOffset(2, 23)
            .setLogoPos(152, 83)
            .setDisableOptimize(true);

        public static class TranscendentPlasmaMixerRecipeMap extends GT_Recipe_Map {

            public TranscendentPlasmaMixerRecipeMap(Collection<GT_Recipe> aRecipeList, String aUnlocalizedName,
                String aLocalName, String aNEIName, String aNEIGUIPath, int aUsualInputCount, int aUsualOutputCount,
                int aMinimalInputItems, int aMinimalInputFluids, int aAmperage, String aNEISpecialValuePre,
                int aNEISpecialValueMultiplier, String aNEISpecialValuePost, boolean aShowVoltageAmperageInNEI,
                boolean aNEIAllowed) {
                super(
                    aRecipeList,
                    aUnlocalizedName,
                    aLocalName,
                    aNEIName,
                    aNEIGUIPath,
                    aUsualInputCount,
                    aUsualOutputCount,
                    aMinimalInputItems,
                    aMinimalInputFluids,
                    aAmperage,
                    aNEISpecialValuePre,
                    aNEISpecialValueMultiplier,
                    aNEISpecialValuePost,
                    aShowVoltageAmperageInNEI,
                    aNEIAllowed);
                useModularUI(true);
                setUsualFluidInputCount(20);
                setUsualFluidOutputCount(1);
                setProgressBarPos(86, 44);
                setNEITransferRect(
                    new Rectangle(
                        progressBarPos.x - (16 / 2),
                        progressBarPos.y,
                        progressBarSize.width + 16,
                        progressBarSize.height));
                setLogoPos(87, 99);
                setNEIBackgroundSize(172, 118);
            }

            @Override
            public List<Pos2d> getItemInputPositions(int itemInputCount) {
                return UIHelper.getGridPositions(itemInputCount, 60, 8, 1);
            }

            @Override
            public List<Pos2d> getFluidInputPositions(int fluidInputCount) {
                return UIHelper.getGridPositions(fluidInputCount, 6, 26, 4, 5);
            }

            @Override
            public List<Pos2d> getFluidOutputPositions(int fluidOutputCount) {
                return UIHelper.getGridPositions(fluidOutputCount, 114, 44, 1);
            }

            @Override
            protected void drawNEIEnergyInfo(NEIRecipeInfo recipeInfo) {
                // These look odd because recipeInfo.recipe.mEUt is actually the EU per litre of fluid processed, not
                // the EU/t.
                drawNEIText(
                    recipeInfo,
                    GT_Utility.trans("152", "Total: ")
                        + formatNumbers(1000L * recipeInfo.recipe.mDuration / 100L * recipeInfo.recipe.mEUt)
                        + " EU");
                // 1000 / (20 ticks * 5 seconds) = 10L/t. 10L/t * x EU/L = 10 * x EU/t.
                long averageUsage = 10L * recipeInfo.recipe.mEUt;
                drawNEIText(
                    recipeInfo,
                    "Average: " + formatNumbers(averageUsage)
                        + " EU/t"
                        + GT_Utility.getTierNameWithParentheses(averageUsage));
            }
        }

        /**
         * Uses {@link GT_RecipeConstants#ADDITIVE_AMOUNT} for coal/charcoal amount.
         */
        public static final GT_Recipe_Map sPrimitiveBlastRecipes = new GT_Recipe_Map(
            new HashSet<>(200),
            "gt.recipe.primitiveblastfurnace",
            "Primitive Blast Furnace",
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Default"),
            3,
            3,
            1,
            0,
            1,
            E,
            1,
            E,
            false,
            true).setProgressBar(GT_UITextures.PROGRESSBAR_ARROW, ProgressBar.Direction.RIGHT)
                .setRecipeEmitter(builder -> {
                    Optional<GT_Recipe> rr = builder.validateInputCount(1, 2)
                        .validateOutputCount(1, 2)
                        .validateNoInputFluid()
                        .validateNoOutputFluid()
                        .noOptimize()
                        .build();
                    if (!rr.isPresent()) return Collections.emptyList();
                    ItemStack aInput1 = builder.getItemInputBasic(0);
                    ItemStack aInput2 = builder.getItemInputBasic(1);
                    ItemStack aOutput1 = builder.getItemOutput(0);
                    ItemStack aOutput2 = builder.getItemOutput(1);
                    if ((aInput1 == null && aInput2 == null) || (aOutput1 == null && aOutput2 == null))
                        return Collections.emptyList();
                    int aCoalAmount = builder.getMetadata(ADDITIVE_AMOUNT);
                    if (aCoalAmount <= 0) return Collections.emptyList();
                    GT_RecipeTemplate coll = asTemplate(rr.get());
                    for (Materials coal : new Materials[] { Materials.Coal, Materials.Charcoal }) {
                        coll.derive()
                            .setInputs(aInput1, aInput2, coal.getGems(aCoalAmount))
                            .setOutputs(aOutput1, aOutput2, Materials.DarkAsh.getDustTiny(aCoalAmount));
                        coll.derive()
                            .setInputs(aInput1, aInput2, coal.getDust(aCoalAmount))
                            .setOutputs(aOutput1, aOutput2, Materials.DarkAsh.getDustTiny(aCoalAmount));
                    }
                    int aDuration = builder.duration;
                    if (Railcraft.isModLoaded()) {
                        coll.derive()
                            .setInputs(aInput1, aInput2, RailcraftToolItems.getCoalCoke(aCoalAmount / 2))
                            .setOutputs(aOutput1, aOutput2, Materials.Ash.getDustTiny(aCoalAmount / 2))
                            .setDuration(aDuration * 2 / 3);
                    }
                    if (GTPlusPlus.isModLoaded()) {
                        ItemStack cactusCoke = GT_ModHandler
                            .getModItem(GTPlusPlus.ID, "itemCactusCoke", aCoalAmount * 2L);
                        ItemStack sugarCoke = GT_ModHandler
                            .getModItem(GTPlusPlus.ID, "itemSugarCoke", aCoalAmount * 2L);
                        coll.derive()
                            .setInputs(aInput1, aInput2, cactusCoke)
                            .setOutputs(aOutput1, aOutput2, Materials.Ash.getDustTiny(aCoalAmount * 2))
                            .setDuration(aDuration * 2 / 3);
                        coll.derive()
                            .setInputs(aInput1, aInput2, sugarCoke)
                            .setOutputs(aOutput1, aOutput2, Materials.Ash.getDustTiny(aCoalAmount * 2))
                            .setDuration(aDuration * 2 / 3);
                    }
                    if ((aInput1 == null || aInput1.stackSize <= 6) && (aInput2 == null || aInput2.stackSize <= 6)
                        && (aOutput1 == null || aOutput1.stackSize <= 6)
                        && (aOutput2 == null || aOutput2.stackSize <= 6)) {
                        // we don't use GT_Utility.mul() here. It does not have the truncating we need here.
                        aInput1 = GT_Utility.multiplyStack(10L, aInput1);
                        aInput2 = GT_Utility.multiplyStack(10L, aInput2);
                        aOutput1 = GT_Utility.multiplyStack(10L, aOutput1);
                        aOutput2 = GT_Utility.multiplyStack(10L, aOutput2);
                        for (Materials coal : new Materials[] { Materials.Coal, Materials.Charcoal }) {
                            coll.derive()
                                .setInputs(aInput1, aInput2, coal.getBlocks(aCoalAmount))
                                .setOutputs(aOutput1, aOutput2, Materials.DarkAsh.getDust(aCoalAmount))
                                .setDuration(aDuration * 10);
                            coll.derive()
                                .setInputs(aInput1, aInput2, coal.getBlocks(aCoalAmount))
                                .setOutputs(aOutput1, aOutput2, Materials.DarkAsh.getDust(aCoalAmount))
                                .setDuration(aDuration * 10);
                        }
                        if (Railcraft.isModLoaded()) {
                            coll.derive()
                                .setInputs(aInput1, aInput2, EnumCube.COKE_BLOCK.getItem(aCoalAmount / 2))
                                .setOutputs(aOutput1, aOutput2, Materials.Ash.getDust(aCoalAmount / 2))
                                .setDuration(aDuration * 20 / 3);
                        }
                    }
                    return coll.getAll();
                })
                .setRecipeConfigFile("primitiveblastfurnace", FIRST_ITEM_INPUT);
        /**
         * Uses {@link GT_RecipeConstants#ADDITIVE_AMOUNT} for TNT/ITNT/... amount. Value is truncated to [0, 64]
         */
        public static final GT_Recipe_Map sImplosionRecipes = new GT_Recipe_Map(
            new HashSet<>(900),
            "gt.recipe.implosioncompressor",
            "Implosion Compressor",
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Default"),
            2,
            2,
            2,
            0,
            1,
            E,
            1,
            E,
            true,
            true).setSlotOverlay(false, false, true, GT_UITextures.OVERLAY_SLOT_IMPLOSION)
                .setSlotOverlay(false, false, false, GT_UITextures.OVERLAY_SLOT_EXPLOSIVE)
                .setRecipeConfigFile("implosion", FIRST_ITEM_INPUT)
                .setRecipeEmitter(b -> {
                    switch (b.getItemInputsBasic().length) {
                        case 0:
                            return Collections.emptyList();
                        case 1:
                            break;
                        default:
                            return b.build()
                                .map(Collections::singletonList)
                                .orElse(Collections.emptyList());
                    }
                    Optional<GT_Recipe> t = b.noOptimize()
                        .duration(20)
                        .eut(30)
                        .validateInputCount(1, 1)
                        .validateOutputCount(1, 2)
                        .build();
                    if (!t.isPresent()) return Collections.emptyList();
                    ItemStack input = b.getItemInputBasic(0);
                    GT_RecipeTemplate coll = asTemplate(t.get());
                    int tExplosives = Math.min(b.getMetadata(ADDITIVE_AMOUNT), 64);
                    int tGunpowder = tExplosives << 1; // Worst
                    int tDynamite = Math.max(1, tExplosives >> 1); // good
                    @SuppressWarnings("UnnecessaryLocalVariable")
                    int tTNT = tExplosives; // Slightly better
                    int tITNT = Math.max(1, tExplosives >> 2); // the best
                    if (tGunpowder < 65) coll.derive()
                        .setInputs(input, ItemList.Block_Powderbarrel.get(tGunpowder));
                    if (tDynamite < 17) coll.derive()
                        .setInputs(input, GT_ModHandler.getIC2Item("dynamite", tDynamite, null));
                    coll.derive()
                        .setInputs(input, new ItemStack(Blocks.tnt, tTNT));
                    coll.derive()
                        .setInputs(input, GT_ModHandler.getIC2Item("industrialTnt", tITNT, null));
                    return coll.getAll();
                })
                .setDisableOptimize(true)
                .setProgressBar(GT_UITextures.PROGRESSBAR_COMPRESS, ProgressBar.Direction.RIGHT);
        public static final GT_Recipe_Map sVacuumRecipes = new GT_Recipe_Map(
            new HashSet<>(305),
            "gt.recipe.vacuumfreezer",
            "Vacuum Freezer",
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Default"),
            1,
            1,
            0,
            0,
            1,
            E,
            1,
            E,
            false,
            true).setProgressBar(GT_UITextures.PROGRESSBAR_ARROW, ProgressBar.Direction.RIGHT)
                .setRecipeConfigFile("vacuumfreezer", FIRST_ITEM_INPUT)
                .setRecipeEmitter(b -> {
                    b.noOptimize();
                    FluidStack in, out;
                    if (isArrayOfLength(b.getItemInputsBasic(), 1) && isArrayOfLength(b.getItemOutputs(), 1)
                        && isArrayEmptyOrNull(b.getFluidInputs())
                        && isArrayEmptyOrNull(b.getFluidOutputs())
                        && (in = GT_Utility.getFluidForFilledItem(b.getItemInputBasic(0), true)) != null
                        && (out = GT_Utility.getFluidForFilledItem(b.getItemOutput(0), true)) != null) {
                        return Arrays.asList(
                            b.build()
                                .get(),
                            b.fluidInputs(in)
                                .fluidOutputs(out)
                                .noItemInputs()
                                .noItemOutputs()
                                .build()
                                .get());
                    }
                    return buildOrEmpty(b);
                })
                .setUsualFluidInputCount(2);
        /**
         * using {@code .addTo(sChemicalRecipes)} will cause the recipe to be added to single block recipe map ONLY!
         * use {@link GT_RecipeConstants#UniversalChemical} to add to both.
         */
        public static final GT_Recipe_Map sChemicalRecipes = new GT_Recipe_Map(
            new HashSet<>(1170),
            "gt.recipe.chemicalreactor",
            null,
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "ChemicalReactor"),
            2,
            2,
            1,
            0,
            1,
            E,
            1,
            E,
            true,
            true).setSlotOverlay(false, false, true, GT_UITextures.OVERLAY_SLOT_MOLECULAR_1)
                .setSlotOverlay(false, false, false, GT_UITextures.OVERLAY_SLOT_MOLECULAR_2)
                .setSlotOverlay(true, false, GT_UITextures.OVERLAY_SLOT_MOLECULAR_3)
                .setSlotOverlay(false, true, GT_UITextures.OVERLAY_SLOT_VIAL_1)
                .setSlotOverlay(true, true, GT_UITextures.OVERLAY_SLOT_VIAL_2)
                .setRecipeConfigFile("chemicalreactor", FIRST_ITEM_OR_FLUID_OUTPUT)
                .setProgressBar(GT_UITextures.PROGRESSBAR_ARROW_MULTIPLE, ProgressBar.Direction.RIGHT)
                .setDisableOptimize(true);
        /**
         * using {@code .addTo(sMultiblockChemicalRecipes)} will cause the recipe to be added to multiblock recipe map
         * ONLY!
         * use {@link GT_RecipeConstants#UniversalChemical} to add to both.
         */
        public static final GT_Recipe_Map sMultiblockChemicalRecipes = //
            new GT_Recipe_Map_LargeChemicalReactor()
                .setProgressBar(GT_UITextures.PROGRESSBAR_ARROW_MULTIPLE, ProgressBar.Direction.RIGHT)
                .setUsualFluidInputCount(6)
                .setUsualFluidOutputCount(6)
                .setDisableOptimize(true);
        public static final GT_Recipe_Map sDistillationRecipes = //
            new GT_Recipe_Map_DistillationTower().setRecipeConfigFile("distillation", FIRST_FLUIDSTACK_INPUT)
                .setProgressBar(GT_UITextures.PROGRESSBAR_ARROW_MULTIPLE, ProgressBar.Direction.RIGHT)
                .setUsualFluidOutputCount(11)
                .setDisableOptimize(true);
        public static final GT_Recipe_Map_OilCracker sCrackingRecipes = (GT_Recipe_Map_OilCracker) //
        new GT_Recipe_Map_OilCracker().setRecipeConfigFile("cracking", FIRST_FLUIDSTACK_INPUT)
            .setProgressBar(GT_UITextures.PROGRESSBAR_ARROW_MULTIPLE, ProgressBar.Direction.RIGHT)
            .setUsualFluidInputCount(2);
        /**
         * @deprecated Use sCrackingRecipes instead
         */
        @Deprecated
        public static final GT_Recipe_Map sCrakingRecipes = sCrackingRecipes;

        public static final GT_Recipe_Map sPyrolyseRecipes = new GT_Recipe_Map(
            new HashSet<>(150),
            "gt.recipe.pyro",
            "Pyrolyse Oven",
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Default"),
            2,
            1,
            1,
            0,
            1,
            E,
            1,
            E,
            true,
            true).setProgressBar(GT_UITextures.PROGRESSBAR_ARROW, ProgressBar.Direction.RIGHT)
                .setDisableOptimize(true)
                .setRecipeConfigFile("pyrolyse", FIRST_ITEM_INPUT);
        public static final GT_Recipe_Map sWiremillRecipes = new GT_Recipe_Map(
            new HashSet<>(450),
            "gt.recipe.wiremill",
            null,
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Wiremill"),
            2,
            1,
            1,
            0,
            1,
            E,
            1,
            E,
            true,
            true).setSlotOverlay(false, false, GT_UITextures.OVERLAY_SLOT_WIREMILL)
                .setRecipeConfigFile("wiremill", FIRST_ITEM_INPUT)
                .setProgressBar(GT_UITextures.PROGRESSBAR_WIREMILL, ProgressBar.Direction.RIGHT);
        public static final GT_Recipe_Map sBenderRecipes = new GT_Recipe_Map(
            new HashSet<>(5000),
            "gt.recipe.metalbender",
            null,
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Bender"),
            2,
            1,
            2,
            0,
            1,
            E,
            1,
            E,
            true,
            true).setSlotOverlay(false, false, GT_UITextures.OVERLAY_SLOT_BENDER)
                .setRecipeConfigFile("bender", FIRST_ITEM_INPUT)
                .setProgressBar(GT_UITextures.PROGRESSBAR_BENDING, ProgressBar.Direction.RIGHT);
        public static final GT_Recipe_Map sAlloySmelterRecipes = new GT_Recipe_Map(
            new HashSet<>(12000),
            "gt.recipe.alloysmelter",
            null,
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "AlloySmelter"),
            2,
            1,
            2,
            0,
            1,
            E,
            1,
            E,
            true,
            true).setSlotOverlay(false, false, GT_UITextures.OVERLAY_SLOT_FURNACE)
                .setRecipeEmitter(b -> {
                    if (Materials.Graphite.contains(b.getItemInputBasic(0))) return Collections.emptyList();
                    if (GT_Utility.isArrayOfLength(b.getItemInputsBasic(), 1)) {
                        ItemStack aInput1 = b.getItemInputBasic(0);
                        if (((OrePrefixes.ingot.contains(aInput1)) || (OrePrefixes.dust.contains(aInput1))
                            || (OrePrefixes.gem.contains(aInput1)))) return Collections.emptyList();
                    }
                    return buildOrEmpty(
                        b.validateNoInputFluid()
                            .validateNoOutputFluid()
                            .validateInputCount(1, 2)
                            .validateOutputCount(1, 1));
                })
                .setRecipeConfigFile(
                    "alloysmelting",
                    r -> GT_Config
                        .getStackConfigName(GT_Utility.isArrayOfLength(r.mInputs, 1) ? r.mInputs[0] : r.mOutputs[0]))
                .setProgressBar(GT_UITextures.PROGRESSBAR_ARROW, ProgressBar.Direction.RIGHT)
                .setSlotOverlaySteam(false, GT_UITextures.OVERLAY_SLOT_FURNACE_STEAM)
                .setProgressBarSteam(GT_UITextures.PROGRESSBAR_ARROW_STEAM);
        public static final GT_Recipe_Map sAssemblerRecipes = new GT_Recipe_Map_Assembler(
            new HashSet<>(8200),
            "gt.recipe.assembler",
            null,
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Assembler2"),
            9,
            1,
            1,
            0,
            1,
            E,
            1,
            E,
            true,
            true).setSlotOverlay(false, false, GT_UITextures.OVERLAY_SLOT_CIRCUIT)
                .setRecipeConfigFile("assembling", FIRST_ITEM_OUTPUT)
                .setProgressBar(GT_UITextures.PROGRESSBAR_ASSEMBLE, ProgressBar.Direction.RIGHT)
                .setDisableOptimize(true);
        public static final GT_Recipe_Map sCircuitAssemblerRecipes = new GT_Recipe_Map_Assembler(
            new HashSet<>(605),
            "gt.recipe.circuitassembler",
            null,
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "CircuitAssembler"),
            6,
            1,
            1,
            0,
            1,
            E,
            1,
            E,
            true,
            true).setNEIUnificateOutput(!NEICustomDiagrams.isModLoaded())
                .setRecipeConfigFile("circuitassembler", FIRST_ITEM_OUTPUT)
                .setSlotOverlay(false, false, GT_UITextures.OVERLAY_SLOT_CIRCUIT)
                .setProgressBar(GT_UITextures.PROGRESSBAR_CIRCUIT_ASSEMBLER, ProgressBar.Direction.RIGHT);
        public static final GT_Recipe_Map sCannerRecipes = new GT_Recipe_Map(
            new HashSet<>(900),
            "gt.recipe.canner",
            null,
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Canner"),
            2,
            2,
            1,
            0,
            1,
            E,
            1,
            E,
            true,
            true).setSlotOverlay(false, false, true, GT_UITextures.OVERLAY_SLOT_CANNER)
                .setRecipeConfigFile("canning", FIRST_ITEM_INPUT)
                .setSlotOverlay(false, false, false, GT_UITextures.OVERLAY_SLOT_CANISTER)
                .setProgressBar(GT_UITextures.PROGRESSBAR_CANNER, ProgressBar.Direction.RIGHT);
        @Deprecated
        public static final GT_Recipe_Map sCNCRecipes = new GT_Recipe_Map(
            new HashSet<>(100),
            "gt.recipe.cncmachine",
            "CNC Machine",
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Default"),
            2,
            1,
            2,
            1,
            1,
            E,
            1,
            E,
            true,
            true).setProgressBar(GT_UITextures.PROGRESSBAR_ARROW, ProgressBar.Direction.RIGHT);
        public static final GT_Recipe_Map sLatheRecipes = new GT_Recipe_Map(
            new HashSet<>(1150),
            "gt.recipe.lathe",
            null,
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Lathe"),
            1,
            2,
            1,
            0,
            1,
            E,
            1,
            E,
            true,
            true).setSlotOverlay(false, false, GT_UITextures.OVERLAY_SLOT_ROD_1)
                .setSlotOverlay(false, true, true, GT_UITextures.OVERLAY_SLOT_ROD_2)
                .setSlotOverlay(false, true, false, GT_UITextures.OVERLAY_SLOT_DUST)
                .setRecipeConfigFile("lathe", FIRST_ITEM_INPUT)
                .setProgressBar(GT_UITextures.PROGRESSBAR_LATHE, ProgressBar.Direction.RIGHT)
                .addSpecialTexture(5, 18, 98, 24, GT_UITextures.PROGRESSBAR_LATHE_BASE);
        public static final GT_Recipe_Map sCutterRecipes = new GT_Recipe_Map(
            new HashSet<>(5125),
            "gt.recipe.cuttingsaw",
            null,
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Cutter4"),
            2,
            4,
            1,
            1,
            1,
            E,
            1,
            E,
            true,
            true).setSlotOverlay(false, false, GT_UITextures.OVERLAY_SLOT_BOX)
                .setSlotOverlay(false, true, true, GT_UITextures.OVERLAY_SLOT_CUTTER_SLICED)
                .setSlotOverlay(false, true, false, GT_UITextures.OVERLAY_SLOT_DUST)
                .setRecipeEmitter(b -> {
                    b.validateInputCount(1, 2)
                        .validateOutputCount(1, 4)
                        .validateNoOutputFluid();
                    if (b.getFluidInputs() != null || !b.isValid())
                        return buildOrEmpty(b.validateInputFluidCount(1, 1));
                    int aDuration = b.getDuration(), aEUt = b.getEUt();
                    Collection<GT_Recipe> ret = new ArrayList<>();
                    b.copy()
                        .fluidInputs(Materials.Water.getFluid(GT_Utility.clamp(aDuration * aEUt / 320, 4, 1000)))
                        .duration(aDuration * 2)
                        .build()
                        .ifPresent(ret::add);
                    b.copy()
                        .fluidInputs(GT_ModHandler.getDistilledWater(GT_Utility.clamp(aDuration * aEUt / 426, 3, 750)))
                        .duration(aDuration * 2)
                        .build()
                        .ifPresent(ret::add);
                    b.fluidInputs(Materials.Lubricant.getFluid(GT_Utility.clamp(aDuration * aEUt / 1280, 1, 250)))
                        .duration(aDuration)
                        .build()
                        .ifPresent(ret::add);
                    return ret;
                })
                .setRecipeConfigFile("cutting", FIRST_ITEM_INPUT)
                .setProgressBar(GT_UITextures.PROGRESSBAR_CUT, ProgressBar.Direction.RIGHT);
        public static final GT_Recipe_Map sSlicerRecipes = new GT_Recipe_Map(
            new HashSet<>(20),
            "gt.recipe.slicer",
            null,
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Slicer"),
            2,
            1,
            2,
            0,
            1,
            E,
            1,
            E,
            true,
            true).setSlotOverlay(false, false, true, GT_UITextures.OVERLAY_SLOT_SQUARE)
                .setSlotOverlay(false, false, false, GT_UITextures.OVERLAY_SLOT_SLICE_SHAPE)
                .setSlotOverlay(false, true, GT_UITextures.OVERLAY_SLOT_SLICER_SLICED)
                .setRecipeConfigFile("slicer", FIRST_ITEM_OUTPUT)
                .setProgressBar(GT_UITextures.PROGRESSBAR_SLICE, ProgressBar.Direction.RIGHT);
        public static final GT_Recipe_Map sExtruderRecipes = new GT_Recipe_Map(
            new HashSet<>(13000),
            "gt.recipe.extruder",
            null,
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Extruder"),
            2,
            1,
            2,
            0,
            1,
            E,
            1,
            E,
            true,
            true).setSlotOverlay(false, false, false, GT_UITextures.OVERLAY_SLOT_EXTRUDER_SHAPE)
                .setRecipeConfigFile("extruder", FIRST_ITEM_OUTPUT)
                .setProgressBar(GT_UITextures.PROGRESSBAR_EXTRUDE, ProgressBar.Direction.RIGHT);

        public static final GT_Recipe_Map sHammerRecipes = new GT_Recipe_Map(
            new HashSet<>(3800),
            "gt.recipe.hammer",
            null,
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Hammer"),
            2,
            2,
            1,
            0,
            1,
            E,
            1,
            E,
            true,
            true).setUsualFluidInputCount(2)
                .setUsualFluidOutputCount(2)
                .setSlotOverlay(false, false, GT_UITextures.OVERLAY_SLOT_HAMMER)
                .setRecipeConfigFile("forgehammer", FIRST_ITEM_OUTPUT)
                .setProgressBar(GT_UITextures.PROGRESSBAR_HAMMER, ProgressBar.Direction.DOWN)
                .addSpecialTexture(20, 6, 78, 42, GT_UITextures.PROGRESSBAR_HAMMER_BASE)
                .setSlotOverlaySteam(false, GT_UITextures.OVERLAY_SLOT_HAMMER_STEAM)
                .setProgressBarSteam(GT_UITextures.PROGRESSBAR_HAMMER_STEAM)
                .addSpecialTextureSteam(20, 6, 78, 42, GT_UITextures.PROGRESSBAR_HAMMER_BASE_STEAM);
        public static final GT_Recipe_Map sAmplifiers = new GT_Recipe_Map(
            new HashSet<>(2),
            "gt.recipe.uuamplifier",
            null,
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Amplifabricator"),
            1,
            0,
            1,
            0,
            1,
            E,
            1,
            E,
            true,
            true).setSlotOverlay(false, false, GT_UITextures.OVERLAY_SLOT_CENTRIFUGE)
                .setSlotOverlay(true, true, GT_UITextures.OVERLAY_SLOT_UUA)
                .setRecipeConfigFile("amplifier", FIRST_ITEM_INPUT)
                .setProgressBar(GT_UITextures.PROGRESSBAR_EXTRACT, ProgressBar.Direction.RIGHT);
        public static final GT_Recipe_Map sMassFabFakeRecipes = new GT_Recipe_Map(
            new HashSet<>(2),
            "gt.recipe.massfab",
            null,
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Massfabricator"),
            1,
            0,
            1,
            0,
            8,
            E,
            1,
            E,
            true,
            true).setSlotOverlay(true, false, GT_UITextures.OVERLAY_SLOT_UUA)
                .setSlotOverlay(true, true, GT_UITextures.OVERLAY_SLOT_UUM)
                .setProgressBar(GT_UITextures.PROGRESSBAR_ARROW, ProgressBar.Direction.RIGHT);
        public static final GT_Recipe_Map_Fuel sDieselFuels = (GT_Recipe_Map_Fuel) new GT_Recipe_Map_Fuel(
            new HashSet<>(20),
            "gt.recipe.dieselgeneratorfuel",
            "Combustion Generator Fuels",
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Default"),
            1,
            1,
            0,
            0,
            1,
            "Fuel Value: ",
            1000,
            " EU",
            true,
            true).setProgressBar(GT_UITextures.PROGRESSBAR_ARROW, ProgressBar.Direction.RIGHT);
        public static final GT_Recipe_Map_Fuel sExtremeDieselFuels = (GT_Recipe_Map_Fuel) new GT_Recipe_Map_Fuel(
            new HashSet<>(20),
            "gt.recipe.extremedieselgeneratorfuel",
            "Extreme Diesel Engine Fuel",
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Default"),
            1,
            1,
            0,
            0,
            1,
            "Fuel Value: ",
            1000,
            " EU",
            true,
            true).setProgressBar(GT_UITextures.PROGRESSBAR_ARROW, ProgressBar.Direction.RIGHT);
        public static final GT_Recipe_Map_Fuel sTurbineFuels = (GT_Recipe_Map_Fuel) new GT_Recipe_Map_Fuel(
            new HashSet<>(25),
            "gt.recipe.gasturbinefuel",
            "Gas Turbine Fuel",
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Default"),
            1,
            1,
            0,
            0,
            1,
            "Fuel Value: ",
            1000,
            " EU",
            true,
            true).setProgressBar(GT_UITextures.PROGRESSBAR_ARROW, ProgressBar.Direction.RIGHT);
        public static final GT_Recipe_Map_Fuel sHotFuels = new GT_Recipe_Map_Fuel(
            new HashSet<>(10),
            "gt.recipe.thermalgeneratorfuel",
            "Thermal Generator Fuels",
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Default"),
            1,
            1,
            0,
            0,
            1,
            "Fuel Value: ",
            1000,
            " EU",
            true,
            false);
        public static final GT_Recipe_Map_Fuel sDenseLiquidFuels = (GT_Recipe_Map_Fuel) new GT_Recipe_Map_Fuel(
            new HashSet<>(15),
            "gt.recipe.semifluidboilerfuels",
            "Semifluid Boiler Fuels",
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Default"),
            1,
            1,
            0,
            0,
            1,
            "Fuel Value: ",
            1000,
            " EU",
            true,
            true).setProgressBar(GT_UITextures.PROGRESSBAR_ARROW, ProgressBar.Direction.RIGHT);
        public static final GT_Recipe_Map_Fuel sPlasmaFuels = (GT_Recipe_Map_Fuel) new GT_Recipe_Map_Fuel(
            new HashSet<>(100),
            "gt.recipe.plasmageneratorfuels",
            "Plasma Generator Fuels",
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Default"),
            1,
            1,
            0,
            0,
            1,
            "Fuel Value: ",
            1000,
            " EU",
            true,
            true).setProgressBar(GT_UITextures.PROGRESSBAR_ARROW, ProgressBar.Direction.RIGHT);
        public static final GT_Recipe_Map_Fuel sMagicFuels = (GT_Recipe_Map_Fuel) new GT_Recipe_Map_Fuel(
            new HashSet<>(100),
            "gt.recipe.magicfuels",
            "Magic Energy Absorber Fuels",
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Default"),
            1,
            1,
            0,
            0,
            1,
            "Fuel Value: ",
            1000,
            " EU",
            true,
            true).setProgressBar(GT_UITextures.PROGRESSBAR_ARROW, ProgressBar.Direction.RIGHT);
        public static final GT_Recipe_Map_Fuel sSmallNaquadahReactorFuels = (GT_Recipe_Map_Fuel) new GT_Recipe_Map_Fuel(
            new HashSet<>(1),
            "gt.recipe.smallnaquadahreactor",
            "Naquadah Reactor MkI",
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Default"),
            1,
            1,
            0,
            0,
            1,
            "Fuel Value: ",
            1000,
            " EU",
            true,
            true).setProgressBar(GT_UITextures.PROGRESSBAR_ARROW, ProgressBar.Direction.RIGHT);
        public static final GT_Recipe_Map_Fuel sLargeNaquadahReactorFuels = (GT_Recipe_Map_Fuel) new GT_Recipe_Map_Fuel(
            new HashSet<>(1),
            "gt.recipe.largenaquadahreactor",
            "Naquadah Reactor MkII",
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Default"),
            1,
            1,
            0,
            0,
            1,
            "Fuel Value: ",
            1000,
            " EU",
            true,
            true).setProgressBar(GT_UITextures.PROGRESSBAR_ARROW, ProgressBar.Direction.RIGHT);
        public static final GT_Recipe_Map_Fuel sHugeNaquadahReactorFuels = (GT_Recipe_Map_Fuel) new GT_Recipe_Map_Fuel(
            new HashSet<>(1),
            "gt.recipe.fluidnaquadahreactor",
            "Naquadah Reactor MkIII",
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Default"),
            1,
            1,
            0,
            0,
            1,
            "Fuel Value: ",
            1000,
            " EU",
            true,
            true).setProgressBar(GT_UITextures.PROGRESSBAR_ARROW, ProgressBar.Direction.RIGHT);
        public static final GT_Recipe_Map_Fuel sExtremeNaquadahReactorFuels = (GT_Recipe_Map_Fuel) new GT_Recipe_Map_Fuel(
            new HashSet<>(1),
            "gt.recipe.hugenaquadahreactor",
            "Naquadah Reactor MkIV",
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Default"),
            1,
            1,
            0,
            0,
            1,
            "Fuel Value: ",
            1000,
            " EU",
            true,
            true).setProgressBar(GT_UITextures.PROGRESSBAR_ARROW, ProgressBar.Direction.RIGHT);
        public static final GT_Recipe_Map_Fuel sUltraHugeNaquadahReactorFuels = (GT_Recipe_Map_Fuel) new GT_Recipe_Map_Fuel(
            new HashSet<>(1),
            "gt.recipe.extrahugenaquadahreactor",
            "Naquadah Reactor MkV",
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Default"),
            1,
            1,
            0,
            0,
            1,
            "Fuel Value: ",
            1000,
            " EU",
            true,
            true).setProgressBar(GT_UITextures.PROGRESSBAR_ARROW, ProgressBar.Direction.RIGHT);
        public static final GT_Recipe_Map_Fuel sFluidNaquadahReactorFuels = (GT_Recipe_Map_Fuel) new GT_Recipe_Map_Fuel(
            new HashSet<>(1),
            "gt.recipe.fluidfuelnaquadahreactor",
            "Fluid Naquadah Reactor",
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Default"),
            1,
            1,
            0,
            0,
            1,
            "Fuel Value: ",
            1000,
            " EU",
            true,
            true).setProgressBar(GT_UITextures.PROGRESSBAR_ARROW, ProgressBar.Direction.RIGHT);
        public static final GT_Recipe_Map sMultiblockElectrolyzerRecipes = new GT_Recipe_Map(
            new HashSet<>(300),
            "gt.recipe.largeelectrolyzer",
            "Large(PA) Electrolyzer",
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "LCRNEI"),
            1,
            9,
            0,
            0,
            1,
            "",
            0,
            "",
            true,
            false).setRecipeEmitter(GT_RecipeMapUtil::buildRecipeForMultiblock);

        public static final GT_Recipe_Map sMultiblockCentrifugeRecipes = new GT_Recipe_Map(
            new HashSet<>(1200),
            "gt.recipe.largecentrifuge",
            "Large(PA) Centrifuge",
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "LCRNEI"),
            1,
            9,
            0,
            0,
            1,
            "",
            0,
            "",
            true,
            false).setRecipeEmitter(GT_RecipeMapUtil::buildRecipeForMultiblock)
                .setDisableOptimize(true);
        public static final GT_Recipe_Map sMultiblockMixerRecipes = new GT_Recipe_Map(
            new HashSet<>(900),
            "gt.recipe.largemixer",
            "Large(PA) Mixer",
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "LCRNEI"),
            9,
            3,
            0,
            0,
            1,
            "",
            0,
            "",
            true,
            false).setRecipeEmitter(GT_RecipeMapUtil::buildRecipeForMultiblockNoCircuit)
                .setDisableOptimize(true);
        public static final GT_Recipe_Map_LargeBoilerFakeFuels sLargeBoilerFakeFuels = (GT_Recipe_Map_LargeBoilerFakeFuels) new GT_Recipe_Map_LargeBoilerFakeFuels()
            .setProgressBar(GT_UITextures.PROGRESSBAR_ARROW, ProgressBar.Direction.RIGHT)
            .setDisableOptimize(true);

        public static final GT_Recipe_Map sNanoForge = new GT_Recipe_Map(
            new HashSet<>(10),
            "gt.recipe.nanoforge",
            "Nano Forge",
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "LCRNEI"),
            6,
            2,
            2,
            1,
            1,
            "Tier: ",
            1,
            "",
            false,
            true).useModularUI(true)
                .setUsualFluidInputCount(3)
                .setDisableOptimize(true)
                .setSlotOverlay(false, false, true, GT_UITextures.OVERLAY_SLOT_LENS)
                .setProgressBar(GT_UITextures.PROGRESSBAR_ASSEMBLE, ProgressBar.Direction.RIGHT);

        public static final GT_Recipe_Map sPCBFactory = new GT_Recipe_Map(
            new HashSet<>(10),
            "gt.recipe.pcbfactory",
            "PCB Factory",
            null,
            GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "LCRNEI"),
            6,
            9,
            3,
            1,
            1,
            E,
            0,
            E,
            true,
            true).useModularUI(true)
                .setUsualFluidInputCount(3)
                .setUsualFluidOutputCount(0)
                .setDisableOptimize(true)
                .setProgressBar(GT_UITextures.PROGRESSBAR_ASSEMBLE, ProgressBar.Direction.RIGHT)
                .setNEISpecialInfoFormatter((recipeInfo, applyPrefixAndSuffix) -> {
                    List<String> result = new ArrayList<>();
                    int bitmap = recipeInfo.recipe.mSpecialValue;
                    if ((bitmap & 0b1) > 0) {
                        result.add(GT_Utility.trans("336", "PCB Factory Tier: ") + 1);
                    } else if ((bitmap & 0b10) > 0) {
                        result.add(GT_Utility.trans("336", "PCB Factory Tier: ") + 2);
                    } else if ((bitmap & 0b100) > 0) {
                        result.add(GT_Utility.trans("336", "PCB Factory Tier: ") + 3);
                    }
                    if ((bitmap & 0b1000) > 0) {
                        result.add(GT_Utility.trans("337", "Upgrade Required: ") + GT_Utility.trans("338", "Bio"));
                    }
                    return result;
                });

        public static final GT_Recipe_Map_IC2NuclearFake sIC2NuclearFakeRecipe = (GT_Recipe_Map_IC2NuclearFake) new GT_Recipe_Map_IC2NuclearFake()
            .setDisableOptimize(true);

        static {
            sCentrifugeRecipes.addDownstream(sMultiblockCentrifugeRecipes.deepCopyInput());
            sMixerRecipes.addDownstream(sMultiblockMixerRecipes.deepCopyInput());
            sElectrolyzerRecipes.addDownstream(sMultiblockElectrolyzerRecipes.deepCopyInput());
            sDieselFuels.addDownstream(
                IGT_RecipeMap.newRecipeMap(
                    b -> b.build()
                        .map(sLargeBoilerFakeFuels::addDieselRecipe)
                        .map(Collections::singletonList)
                        .orElse(Collections.emptyList())));
            sDenseLiquidFuels.addDownstream(
                IGT_RecipeMap.newRecipeMap(
                    b -> b.build()
                        .map(sLargeBoilerFakeFuels::addDenseLiquidRecipe)
                        .map(Collections::singletonList)
                        .orElse(Collections.emptyList())));
        }

        @Nullable
        public static GT_Recipe_Map findRecipeMap(@Nonnull String unlocalizedName) {
            return sMappings.stream()
                .filter(m -> unlocalizedName.equals(m.mUnlocalizedName))
                .findFirst()
                .orElse(null);
        }

        /**
         * HashMap of Recipes based on their Items
         */
        public final Map<GT_ItemStack, Collection<GT_Recipe>> mRecipeItemMap = new /* Concurrent */ HashMap<>();
        /**
         * HashMap of Recipes based on their Fluids
         */
        public final Map<String, Collection<GT_Recipe>> mRecipeFluidMap = new HashMap<>();

        public final HashSet<String> mRecipeFluidNameMap = new HashSet<>();
        /**
         * The List of all Recipes
         */
        public final Collection<GT_Recipe> mRecipeList;
        /**
         * String used as an unlocalised Name.
         */
        public final String mUnlocalizedName;
        /**
         * String used in NEI for the Recipe Lists. If null it will use the unlocalised Name instead
         */
        public final String mNEIName;
        /**
         * GUI used for NEI Display. Usually the GUI of the Machine itself
         */
        public final String mNEIGUIPath;

        public final String mNEISpecialValuePre, mNEISpecialValuePost;
        public final int mUsualInputCount, mUsualOutputCount, mNEISpecialValueMultiplier, mMinimalInputItems,
            mMinimalInputFluids, mAmperage;
        public final boolean mNEIAllowed, mShowVoltageAmperageInNEI;

        /**
         * Whether to show oredict equivalent outputs when NEI is queried to show recipe
         */
        public boolean mNEIUnificateOutput = true;

        /**
         * Unique identifier for this recipe map. Generated from aUnlocalizedName and a few other parameters. See
         * constructor for details.
         */
        public final String mUniqueIdentifier;

        /**
         * Whether this recipe map contains any fluid outputs.
         */
        private boolean mHasFluidOutputs = false;

        /**
         * Whether this recipe map contains special slot inputs.
         */
        private boolean mUsesSpecialSlot = false;

        /**
         * Whether this recipemap checks for equality of special slot when searching recipe.
         */
        private boolean isSpecialSlotSensitive = false;

        /**
         * How many fluid inputs does this recipemap has at most. Currently used only for NEI slot placements and does
         * not actually restrict number of fluids used in the recipe.
         */
        private int usualFluidInputCount;

        /**
         * How many fluid outputs does this recipemap has at most. Currently used only for NEI slot placements and does
         * not actually restrict number of fluids used in the recipe.
         */
        private int usualFluidOutputCount;

        /**
         * Whether to use ModularUI for slot placements.
         */
        public boolean useModularUI = false;

        /**
         * Overlays used for GUI. 1 = If it's fluid slot. 2 = If it's output slot. 4 = If it's first slot in the same
         * section, e.g. first slot in the item output slots 8 = If it's special item slot.
         */
        private final TByteObjectMap<IDrawable> slotOverlays = new TByteObjectHashMap<>();

        /**
         * Overlays used for GUI on steam machine. 1 = If it's fluid slot. 2 = If it's output slot. 4 = If it's first
         * slot in the same section, e.g. first slot in the item output slots 8 = If it's special item slot.
         */
        private final TByteObjectMap<SteamTexture> slotOverlaysSteam = new TByteObjectHashMap<>();

        /**
         * Progressbar used for BasicMachine GUI and/or NEI. Unless specified, size should be (20, 36), consisting of
         * two parts; First is (20, 18) size of "empty" image at the top, Second is (20, 18) size of "filled" image at
         * the bottom.
         */
        private FallbackableUITexture progressBarTexture;

        /**
         * Progressbar used for steam machine GUI and/or NEI. Unless specified, size should be (20, 36), consisting of
         * two parts; First is (20, 18) size of "empty" image at the top, Second is (20, 18) size of "filled" image at
         * the bottom.
         */
        private FallbackableSteamTexture progressBarTextureSteam;

        public ProgressBar.Direction progressBarDirection = ProgressBar.Direction.RIGHT;

        public Size progressBarSize = new Size(20, 18);

        public Pos2d progressBarPos = new Pos2d(78, 24);

        public Rectangle neiTransferRect = new Rectangle(
            progressBarPos.x - (16 / 2),
            progressBarPos.y,
            progressBarSize.width + 16,
            progressBarSize.height);

        /**
         * Image size in direction of progress. Used for non-smooth rendering.
         */
        private int progressBarImageSize;

        /**
         * Additional textures shown on GUI.
         */
        public final List<Pair<IDrawable, Pair<Size, Pos2d>>> specialTextures = new ArrayList<>();

        /**
         * Additional textures shown on steam machine GUI.
         */
        public final List<Pair<SteamTexture, Pair<Size, Pos2d>>> specialTexturesSteam = new ArrayList<>();

        public IDrawable logo = GT_UITextures.PICTURE_GT_LOGO_17x17_TRANSPARENT;

        public Pos2d logoPos = new Pos2d(152, 63);

        public Size logoSize = new Size(17, 17);

        public Pos2d neiBackgroundOffset = new Pos2d(2, 3);

        public Size neiBackgroundSize = new Size(172, 82);

        protected final GT_GUIColorOverride colorOverride;
        private int neiTextColorOverride = -1;

        private INEISpecialInfoFormatter neiSpecialInfoFormatter;

        private final boolean checkForCollision = true;
        private boolean allowNoInput;
        private boolean allowNoInputFluid;
        private boolean allowNoOutput;
        private boolean allowNoOutputFluid;
        private boolean disableOptimize = false;
        private Function<? super GT_RecipeBuilder, ? extends Iterable<? extends GT_Recipe>> recipeEmitter = this::defaultBuildRecipe;
        private Function<? super GT_Recipe, ? extends GT_Recipe> specialHandler;
        private String recipeConfigCategory;
        private Function<? super GT_Recipe, String> recipeConfigKeyConvertor;
        private final List<IGT_RecipeMap> downstreams = new ArrayList<>(0);

        /**
         * Flag if a comparator should be used to search the recipe in NEI (which is defined in {@link Power}). Else
         * only the voltage will be used to find recipes
         */
        public boolean useComparatorForNEI;

        /**
         * Whether to render the actual size of stacks or a size of 1.
         */
        public boolean renderRealStackSizes = true;

        /**
         * Initialises a new type of Recipe Handler.
         *
         * @param aRecipeList                a List you specify as Recipe List. Usually just an ArrayList with a
         *                                   pre-initialised Size.
         * @param aUnlocalizedName           the unlocalised Name of this Recipe Handler, used mainly for NEI.
         * @param aLocalName                 @deprecated the displayed Name inside the NEI Recipe GUI for optionally
         *                                   registering aUnlocalizedName
         *                                   with the language manager
         * @param aNEIGUIPath                the displayed GUI Texture, usually just a Machine GUI. Auto-Attaches ".png"
         *                                   if forgotten.
         * @param aUsualInputCount           the usual amount of Input Slots this Recipe Class has.
         * @param aUsualOutputCount          the usual amount of Output Slots this Recipe Class has.
         * @param aNEISpecialValuePre        the String in front of the Special Value in NEI.
         * @param aNEISpecialValueMultiplier the Value the Special Value is getting Multiplied with before displaying
         * @param aNEISpecialValuePost       the String after the Special Value. Usually for a Unit or something.
         * @param aNEIAllowed                if NEI is allowed to display this Recipe Handler in general.
         */
        public GT_Recipe_Map(Collection<GT_Recipe> aRecipeList, String aUnlocalizedName, String aLocalName,
            String aNEIName, String aNEIGUIPath, int aUsualInputCount, int aUsualOutputCount, int aMinimalInputItems,
            int aMinimalInputFluids, int aAmperage, String aNEISpecialValuePre, int aNEISpecialValueMultiplier,
            String aNEISpecialValuePost, boolean aShowVoltageAmperageInNEI, boolean aNEIAllowed) {
            sMappings.add(this);
            mNEIAllowed = aNEIAllowed;
            mShowVoltageAmperageInNEI = aShowVoltageAmperageInNEI;
            mRecipeList = aRecipeList;
            mNEIName = aNEIName == null ? aUnlocalizedName : aNEIName;
            mNEIGUIPath = aNEIGUIPath.endsWith(".png") ? aNEIGUIPath : aNEIGUIPath + ".png";
            mNEISpecialValuePre = aNEISpecialValuePre;
            mNEISpecialValueMultiplier = aNEISpecialValueMultiplier;
            mNEISpecialValuePost = aNEISpecialValuePost;
            mAmperage = aAmperage;
            mUsualInputCount = aUsualInputCount;
            mUsualOutputCount = aUsualOutputCount;
            mMinimalInputItems = aMinimalInputItems;
            mMinimalInputFluids = aMinimalInputFluids;
            GregTech_API.sItemStackMappings.add(mRecipeItemMap);
            mUnlocalizedName = aUnlocalizedName;
            if (aLocalName != null) {
                GT_LanguageManager.addStringLocalization(mUnlocalizedName, aLocalName);
            }
            mUniqueIdentifier = String.format(
                "%s_%d_%d_%d_%d_%d",
                aUnlocalizedName,
                aAmperage,
                aUsualInputCount,
                aUsualOutputCount,
                aMinimalInputFluids,
                aMinimalInputItems);
            progressBarTexture = new FallbackableUITexture(
                UITexture.fullImage(GregTech.ID, "gui/progressbar/" + mUnlocalizedName),
                GT_UITextures.PROGRESSBAR_ARROW);
            colorOverride = GT_GUIColorOverride.get(ModularUITextures.VANILLA_BACKGROUND.location);
            if (sIndexedMappings.put(mUniqueIdentifier, this) != null)
                throw new IllegalArgumentException("Duplicate recipe map registered: " + mUniqueIdentifier);
        }

        @Deprecated
        public GT_Recipe_Map(Collection<GT_Recipe> aRecipeList, String aUnlocalizedName, String aLocalName,
            String aNEIName, String aNEIGUIPath, int aUsualInputCount, int aUsualOutputCount, int aMinimalInputItems,
            int aMinimalInputFluids, int aAmperage, String aNEISpecialValuePre, int aNEISpecialValueMultiplier,
            String aNEISpecialValuePost, boolean aShowVoltageAmperageInNEI, boolean aNEIAllowed,
            boolean aNEIUnificateOutput) {
            this(
                aRecipeList,
                aUnlocalizedName,
                aLocalName,
                aNEIName,
                aNEIGUIPath,
                aUsualInputCount,
                aUsualOutputCount,
                aMinimalInputItems,
                aMinimalInputFluids,
                aAmperage,
                aNEISpecialValuePre,
                aNEISpecialValueMultiplier,
                aNEISpecialValuePost,
                aShowVoltageAmperageInNEI,
                aNEIAllowed);
            setNEIUnificateOutput(aNEIUnificateOutput);
        }

        public GT_Recipe_Map setDisableOptimize(boolean disableOptimize) {
            this.disableOptimize = disableOptimize;
            return this;
        }

        public GT_Recipe_Map setSpecialSlotSensitive(boolean isSpecialSlotSensitive) {
            this.isSpecialSlotSensitive = isSpecialSlotSensitive;
            return this;
        }

        public GT_Recipe_Map setNEIUnificateOutput(boolean mNEIUnificateOutput) {
            this.mNEIUnificateOutput = mNEIUnificateOutput;
            return this;
        }

        public GT_Recipe_Map useComparatorForNEI(boolean use) {
            this.useComparatorForNEI = use;
            return this;
        }

        public GT_Recipe_Map setRenderRealStackSizes(boolean renderRealStackSizes) {
            this.renderRealStackSizes = renderRealStackSizes;
            return this;
        }

        public GT_Recipe_Map useModularUI(boolean use) {
            this.useModularUI = use;
            return this;
        }

        public GT_Recipe_Map setSlotOverlay(boolean isFluid, boolean isOutput, boolean isFirst, boolean isSpecial,
            IDrawable slotOverlay) {
            useModularUI(true);
            this.slotOverlays.put(
                (byte) ((isFluid ? 1 : 0) + (isOutput ? 2 : 0) + (isFirst ? 4 : 0) + (isSpecial ? 8 : 0)),
                slotOverlay);
            return this;
        }

        public GT_Recipe_Map setSlotOverlay(boolean isFluid, boolean isOutput, boolean isFirst, IDrawable slotOverlay) {
            return setSlotOverlay(isFluid, isOutput, isFirst, false, slotOverlay);
        }

        public GT_Recipe_Map setSlotOverlay(boolean isFluid, boolean isOutput, IDrawable slotOverlay) {
            return setSlotOverlay(isFluid, isOutput, true, slotOverlay)
                .setSlotOverlay(isFluid, isOutput, false, slotOverlay);
        }

        public GT_Recipe_Map setSlotOverlaySteam(boolean isFluid, boolean isOutput, boolean isFirst, boolean isSpecial,
            SteamTexture slotOverlay) {
            useModularUI(true);
            this.slotOverlaysSteam.put(
                (byte) ((isFluid ? 1 : 0) + (isOutput ? 2 : 0) + (isFirst ? 4 : 0) + (isSpecial ? 8 : 0)),
                slotOverlay);
            return this;
        }

        public GT_Recipe_Map setSlotOverlaySteam(boolean isOutput, boolean isFirst, SteamTexture slotOverlay) {
            return setSlotOverlaySteam(false, isOutput, isFirst, false, slotOverlay);
        }

        public GT_Recipe_Map setSlotOverlaySteam(boolean isOutput, SteamTexture slotOverlay) {
            return setSlotOverlaySteam(false, isOutput, true, false, slotOverlay)
                .setSlotOverlaySteam(false, isOutput, false, false, slotOverlay);
        }

        public GT_Recipe_Map setProgressBar(UITexture progressBarTexture, ProgressBar.Direction progressBarDirection) {
            return setProgressBarWithFallback(
                new FallbackableUITexture(
                    UITexture.fullImage(GregTech.ID, "gui/progressbar/" + mUnlocalizedName),
                    progressBarTexture),
                progressBarDirection);
        }

        public GT_Recipe_Map setProgressBar(UITexture progressBarTexture) {
            return setProgressBar(progressBarTexture, ProgressBar.Direction.RIGHT);
        }

        /**
         * Some resource packs want to use custom progress bar textures even for plain arrow. This method allows them to
         * add unique textures, yet other packs don't need to make textures for every recipemap.
         */
        public GT_Recipe_Map setProgressBarWithFallback(FallbackableUITexture progressBarTexture,
            ProgressBar.Direction progressBarDirection) {
            useModularUI(true);
            this.progressBarTexture = progressBarTexture;
            this.progressBarDirection = progressBarDirection;
            return this;
        }

        public GT_Recipe_Map setProgressBarSteam(SteamTexture progressBarTexture) {
            return setProgressBarSteamWithFallback(
                new FallbackableSteamTexture(
                    SteamTexture.fullImage(GregTech.ID, "gui/progressbar/" + mUnlocalizedName + "_%s"),
                    progressBarTexture));
        }

        public GT_Recipe_Map setProgressBarSteamWithFallback(FallbackableSteamTexture progressBarTexture) {
            this.progressBarTextureSteam = progressBarTexture;
            return this;
        }

        public GT_Recipe_Map setProgressBarSize(int x, int y) {
            useModularUI(true);
            this.progressBarSize = new Size(x, y);
            return this;
        }

        public GT_Recipe_Map setProgressBarPos(int x, int y) {
            useModularUI(true);
            this.progressBarPos = new Pos2d(x, y);
            return this;
        }

        public GT_Recipe_Map setProgressBarImageSize(int progressBarImageSize) {
            useModularUI(true);
            this.progressBarImageSize = progressBarImageSize;
            return this;
        }

        public GT_Recipe_Map setNEITransferRect(Rectangle neiTransferRect) {
            useModularUI(true);
            this.neiTransferRect = neiTransferRect;
            return this;
        }

        public GT_Recipe_Map addSpecialTexture(int width, int height, int x, int y, IDrawable texture) {
            useModularUI(true);
            specialTextures
                .add(new ImmutablePair<>(texture, new ImmutablePair<>(new Size(width, height), new Pos2d(x, y))));
            return this;
        }

        public GT_Recipe_Map addSpecialTextureSteam(int width, int height, int x, int y, SteamTexture texture) {
            useModularUI(true);
            specialTexturesSteam
                .add(new ImmutablePair<>(texture, new ImmutablePair<>(new Size(width, height), new Pos2d(x, y))));
            return this;
        }

        public GT_Recipe_Map setUsualFluidInputCount(int usualFluidInputCount) {
            useModularUI(true);
            this.usualFluidInputCount = usualFluidInputCount;
            return this;
        }

        public GT_Recipe_Map setUsualFluidOutputCount(int usualFluidOutputCount) {
            useModularUI(true);
            this.usualFluidOutputCount = usualFluidOutputCount;
            return this;
        }

        public GT_Recipe_Map setLogo(IDrawable logo) {
            useModularUI(true);
            this.logo = logo;
            return this;
        }

        public GT_Recipe_Map setLogoPos(int x, int y) {
            useModularUI(true);
            this.logoPos = new Pos2d(x, y);
            return this;
        }

        public GT_Recipe_Map setLogoSize(int width, int height) {
            useModularUI(true);
            this.logoSize = new Size(width, height);
            return this;
        }

        public GT_Recipe_Map setNEIBackgroundOffset(int x, int y) {
            useModularUI(true);
            this.neiBackgroundOffset = new Pos2d(x, y);
            return this;
        }

        public GT_Recipe_Map setNEIBackgroundSize(int width, int height) {
            useModularUI(true);
            this.neiBackgroundSize = new Size(width, height);
            return this;
        }

        public GT_Recipe_Map setNEISpecialInfoFormatter(INEISpecialInfoFormatter neiSpecialInfoFormatter) {
            this.neiSpecialInfoFormatter = neiSpecialInfoFormatter;
            return this;
        }

        /**
         * Change how recipes are emitted by a particular recipe builder. Can emit multiple recipe per builder.
         */
        public GT_Recipe_Map setRecipeEmitter(
            Function<? super GT_RecipeBuilder, ? extends Iterable<? extends GT_Recipe>> func) {
            this.recipeEmitter = func;
            return this;
        }

        /**
         * Change how recipes are emitted by a particular recipe builder. Can emit multiple recipe per builder.
         * <p>
         * Unlike {@link #setRecipeEmitter(Function)}, this one does not clear the existing recipe being emitted, if any
         */
        public GT_Recipe_Map combineRecipeEmitter(
            Function<? super GT_RecipeBuilder, ? extends Iterable<? extends GT_Recipe>> func) {
            // move recipeEmitter to local variable, so lambda capture the function itself instead of this
            Function<? super GT_RecipeBuilder, ? extends Iterable<? extends GT_Recipe>> cur = recipeEmitter;
            this.recipeEmitter = b -> Iterables.concat(cur.apply(b), func.apply(b));
            return this;
        }

        /**
         * Change how recipes are emitted by a particular recipe builder. Should not return null.
         */
        public GT_Recipe_Map setRecipeEmitterSingle(Function<? super GT_RecipeBuilder, ? extends GT_Recipe> func) {
            return setRecipeEmitter(func.andThen(Collections::singletonList));
        }

        /**
         * Change how recipes are emitted by a particular recipe builder. Effectively add a new recipe per recipe added.
         * func must not return null.
         * <p>
         * Unlike {@link #setRecipeEmitter(Function)}, this one does not clear the existing recipe being emitted, if any
         */
        public GT_Recipe_Map combineRecipeEmitterSingle(Function<? super GT_RecipeBuilder, ? extends GT_Recipe> func) {
            return combineRecipeEmitter(func.andThen(Collections::singletonList));
        }

        private static <T> Function<? super T, ? extends T> withIdentityReturn(Consumer<T> func) {
            return r -> {
                func.accept(r);
                return r;
            };
        }

        /**
         * Run a custom hook on all recipes added <b>via builder</b>. For more complicated behavior subclass this, then
         * override {@link #doAdd(GT_RecipeBuilder)}
         *
         * Recipes added via one of the overloads of addRecipe will NOT be affected by this function.
         */
        public GT_Recipe_Map setRecipeSpecialHandler(Function<? super GT_Recipe, ? extends GT_Recipe> func) {
            this.specialHandler = func;
            return this;
        }

        /**
         * Run a custom hook on all recipes added <b>via builder</b>. For more complicated behavior, create a subclass
         * and override {@link #doAdd(GT_RecipeBuilder)}
         *
         * Recipes added via one of the overloads of addRecipe will NOT be affected by this function.
         */
        public GT_Recipe_Map setRecipeSpecialHandler(Consumer<GT_Recipe> func) {
            return setRecipeSpecialHandler(withIdentityReturn(func));
        }

        /**
         * Run a custom hook on all recipes added <b>via builder</b>. For more complicated behavior subclass this, then
         * override {@link #doAdd(GT_RecipeBuilder)}.
         * <p>
         * Recipes added via one of the overloads of addRecipe will NOT be affected by this function.
         * <p>
         * Unlike {@link #setRecipeSpecialHandler(Function)}, this one will not replace the existing special handler.
         * The supplied function will be given the output of existing handler when a recipe is added.
         */
        public GT_Recipe_Map chainRecipeSpecialHandler(Function<? super GT_Recipe, ? extends GT_Recipe> func) {
            this.specialHandler = specialHandler == null ? func : specialHandler.andThen(func);
            return this;
        }

        /**
         * Run a custom hook on all recipes added <b>via builder</b>. For more complicated behavior subclass this, then
         * override {@link #doAdd(GT_RecipeBuilder)}.
         * <p>
         * Recipes added via one of the overloads of addRecipe will NOT be affected by this function.
         * <p>
         * Unlike {@link #setRecipeSpecialHandler(Function)}, this one will not replace the existing special handler.
         * The supplied function will be given the output of existing handler when a recipe is added.
         */
        public GT_Recipe_Map chainRecipeSpecialHandler(Consumer<GT_Recipe> func) {
            return chainRecipeSpecialHandler(withIdentityReturn(func));
        }

        public GT_Recipe_Map setRecipeConfigFile(String category, Function<? super GT_Recipe, String> keyConvertor) {
            if (StringUtils.isBlank(category) || keyConvertor == null) throw new IllegalArgumentException();
            this.recipeConfigCategory = category;
            this.recipeConfigKeyConvertor = keyConvertor;
            return this;
        }

        @Override
        public void addDownstream(IGT_RecipeMap downstream) {
            this.downstreams.add(downstream);
        }

        public GT_Recipe addRecipe(boolean aOptimize, ItemStack[] aInputs, ItemStack[] aOutputs, Object aSpecial,
            int[] aOutputChances, FluidStack[] aFluidInputs, FluidStack[] aFluidOutputs, int aDuration, int aEUt,
            int aSpecialValue) {
            return addRecipe(
                new GT_Recipe(
                    aOptimize,
                    aInputs,
                    aOutputs,
                    aSpecial,
                    aOutputChances,
                    aFluidInputs,
                    aFluidOutputs,
                    aDuration,
                    aEUt,
                    aSpecialValue));
        }

        public GT_Recipe addRecipe(int[] aOutputChances, FluidStack[] aFluidInputs, FluidStack[] aFluidOutputs,
            int aDuration, int aEUt, int aSpecialValue) {
            return addRecipe(
                new GT_Recipe(
                    false,
                    null,
                    null,
                    null,
                    aOutputChances,
                    aFluidInputs,
                    aFluidOutputs,
                    aDuration,
                    aEUt,
                    aSpecialValue),
                false,
                false,
                false);
        }

        public GT_Recipe addRecipe(boolean aOptimize, ItemStack[] aInputs, ItemStack[] aOutputs, Object aSpecial,
            FluidStack[] aFluidInputs, FluidStack[] aFluidOutputs, int aDuration, int aEUt, int aSpecialValue) {
            return addRecipe(
                new GT_Recipe(
                    aOptimize,
                    aInputs,
                    aOutputs,
                    aSpecial,
                    null,
                    aFluidInputs,
                    aFluidOutputs,
                    aDuration,
                    aEUt,
                    aSpecialValue));
        }

        public GT_Recipe addRecipe(GT_Recipe aRecipe) {
            return addRecipe(aRecipe, true, false, false);
        }

        protected GT_Recipe addRecipe(GT_Recipe aRecipe, boolean aCheckForCollisions, boolean aFakeRecipe,
            boolean aHidden) {
            aRecipe.mHidden = aHidden;
            aRecipe.mFakeRecipe = aFakeRecipe;
            if (aRecipe.mFluidInputs.length < mMinimalInputFluids && aRecipe.mInputs.length < mMinimalInputItems)
                return null;
            if (aCheckForCollisions
                && findRecipe(null, false, true, Long.MAX_VALUE, aRecipe.mFluidInputs, aRecipe.mInputs) != null)
                return null;
            return add(aRecipe);
        }

        /**
         * Only used for fake Recipe Handlers to show something in NEI, do not use this for adding actual Recipes!
         * findRecipe wont find fake Recipes, containsInput WILL find fake Recipes
         */
        public GT_Recipe addFakeRecipe(boolean aCheckForCollisions, ItemStack[] aInputs, ItemStack[] aOutputs,
            Object aSpecial, int[] aOutputChances, FluidStack[] aFluidInputs, FluidStack[] aFluidOutputs, int aDuration,
            int aEUt, int aSpecialValue) {
            return addFakeRecipe(
                aCheckForCollisions,
                new GT_Recipe(
                    false,
                    aInputs,
                    aOutputs,
                    aSpecial,
                    aOutputChances,
                    aFluidInputs,
                    aFluidOutputs,
                    aDuration,
                    aEUt,
                    aSpecialValue));
        }

        /**
         * Only used for fake Recipe Handlers to show something in NEI, do not use this for adding actual Recipes!
         * findRecipe wont find fake Recipes, containsInput WILL find fake Recipes
         */
        public GT_Recipe addFakeRecipe(boolean aCheckForCollisions, ItemStack[] aInputs, ItemStack[] aOutputs,
            Object aSpecial, FluidStack[] aFluidInputs, FluidStack[] aFluidOutputs, int aDuration, int aEUt,
            int aSpecialValue) {
            return addFakeRecipe(
                aCheckForCollisions,
                new GT_Recipe(
                    false,
                    aInputs,
                    aOutputs,
                    aSpecial,
                    null,
                    aFluidInputs,
                    aFluidOutputs,
                    aDuration,
                    aEUt,
                    aSpecialValue));
        }

        public GT_Recipe addFakeRecipe(boolean aCheckForCollisions, ItemStack[] aInputs, ItemStack[] aOutputs,
            Object aSpecial, FluidStack[] aFluidInputs, FluidStack[] aFluidOutputs, int aDuration, int aEUt,
            int aSpecialValue, boolean hidden) {
            return addFakeRecipe(
                aCheckForCollisions,
                new GT_Recipe(
                    false,
                    aInputs,
                    aOutputs,
                    aSpecial,
                    null,
                    aFluidInputs,
                    aFluidOutputs,
                    aDuration,
                    aEUt,
                    aSpecialValue),
                hidden);
        }

        public GT_Recipe addFakeRecipe(boolean aCheckForCollisions, ItemStack[] aInputs, ItemStack[] aOutputs,
            Object aSpecial, FluidStack[] aFluidInputs, FluidStack[] aFluidOutputs, int aDuration, int aEUt,
            int aSpecialValue, ItemStack[][] aAlt, boolean hidden) {
            return addFakeRecipe(
                aCheckForCollisions,
                new GT_Recipe_WithAlt(
                    false,
                    aInputs,
                    aOutputs,
                    aSpecial,
                    null,
                    aFluidInputs,
                    aFluidOutputs,
                    aDuration,
                    aEUt,
                    aSpecialValue,
                    aAlt),
                hidden);
        }

        /**
         * Only used for fake Recipe Handlers to show something in NEI, do not use this for adding actual Recipes!
         * findRecipe wont find fake Recipes, containsInput WILL find fake Recipes
         */
        public GT_Recipe addFakeRecipe(boolean aCheckForCollisions, GT_Recipe aRecipe) {
            return addRecipe(aRecipe, aCheckForCollisions, true, false);
        }

        public GT_Recipe addFakeRecipe(boolean aCheckForCollisions, GT_Recipe aRecipe, boolean hidden) {
            return addRecipe(aRecipe, aCheckForCollisions, true, hidden);
        }

        @Nonnull
        @Override
        public Collection<GT_Recipe> doAdd(GT_RecipeBuilder builder) {
            Iterable<? extends GT_Recipe> recipes = recipeEmitter.apply(builder);
            Collection<GT_Recipe> ret = new ArrayList<>();
            for (GT_Recipe r : recipes) {
                if (recipeConfigCategory != null) {
                    String configKey = recipeConfigKeyConvertor.apply(r);
                    if (configKey != null
                        && (r.mDuration = GregTech_API.sRecipeFile.get(recipeConfigCategory, configKey, r.mDuration))
                            <= 0) {
                        continue;
                    }
                }
                if (r.mFluidInputs.length < mMinimalInputFluids && r.mInputs.length < mMinimalInputItems) return null;
                if (r.mSpecialValue == 0) {
                    // new style cleanroom/lowgrav handling
                    int specialValue = 0;
                    if (builder.getMetadata(GT_RecipeConstants.LOW_GRAVITY, false)) specialValue -= 100;
                    if (builder.getMetadata(GT_RecipeConstants.CLEANROOM, false)) specialValue -= 200;
                    for (GT_RecipeBuilder.MetadataIdentifier<Integer> ident : SPECIAL_VALUE_ALIASES) {
                        Integer metadata = builder.getMetadata(ident, null);
                        if (metadata != null) {
                            specialValue = metadata;
                            break;
                        }
                    }
                    r.mSpecialValue = specialValue;
                }
                if (specialHandler != null) r = specialHandler.apply(r);
                if (r == null) continue;
                if (checkForCollision
                    && findRecipe(null, false, true, Long.MAX_VALUE, r.mFluidInputs, r.mInputs) != null) {
                    StringBuilder errorInfo = new StringBuilder();
                    boolean hasAnEntry = false;
                    for (FluidStack fStack : r.mFluidInputs) {
                        if (fStack == null) {
                            continue;
                        }
                        String s = fStack.getLocalizedName();
                        if (s == null) {
                            continue;
                        }
                        if (hasAnEntry) {
                            errorInfo.append("+")
                                .append(s);
                        } else {
                            errorInfo.append(s);
                        }
                        hasAnEntry = true;
                    }
                    for (ItemStack iStack : r.mInputs) {
                        if (iStack == null) {
                            continue;
                        }
                        String s = iStack.getDisplayName();
                        if (hasAnEntry) {
                            errorInfo.append("+")
                                .append(s);
                        } else {
                            errorInfo.append(s);
                        }
                        hasAnEntry = true;
                    }
                    handleRecipeCollision(errorInfo.toString());
                    continue;
                }
                ret.add(add(r));
            }
            if (!ret.isEmpty()) {
                builder.clearInvalid();
                for (IGT_RecipeMap downstream : downstreams) {
                    downstream.doAdd(builder);
                }
            }
            return ret;
        }

        public final Iterable<? extends GT_Recipe> defaultBuildRecipe(GT_RecipeBuilder builder) {
            // TODO sensible validation
            GT_RecipeBuilder b = builder;
            if (disableOptimize && builder.optimize) {
                b = copy(builder, b).noOptimize();
            }
            return buildOrEmpty(b);
        }

        private static GT_RecipeBuilder copy(GT_RecipeBuilder original, GT_RecipeBuilder b) {
            return b == original ? b.copy() : b;
        }

        public GT_Recipe add(GT_Recipe aRecipe) {
            mRecipeList.add(aRecipe);
            for (FluidStack aFluid : aRecipe.mFluidInputs) {
                if (aFluid != null) {
                    Collection<GT_Recipe> tList = mRecipeFluidMap.computeIfAbsent(
                        aFluid.getFluid()
                            .getName(),
                        k -> new HashSet<>(1));
                    tList.add(aRecipe);
                    mRecipeFluidNameMap.add(
                        aFluid.getFluid()
                            .getName());
                }
            }
            if (aRecipe.mFluidOutputs.length != 0) {
                this.mHasFluidOutputs = true;
            }
            if (aRecipe.mSpecialItems != null) {
                this.mUsesSpecialSlot = true;
            }
            return addToItemMap(aRecipe);
        }

        public void reInit() {
            mRecipeItemMap.clear();
            for (GT_Recipe tRecipe : mRecipeList) {
                GT_OreDictUnificator.setStackArray(true, tRecipe.mInputs);
                GT_OreDictUnificator.setStackArray(true, tRecipe.mOutputs);
                addToItemMap(tRecipe);
            }
        }

        /**
         * @return if this Item is a valid Input for any for the Recipes
         */
        public boolean containsInput(ItemStack aStack) {
            return aStack != null && (mRecipeItemMap.containsKey(new GT_ItemStack(aStack))
                || mRecipeItemMap.containsKey(new GT_ItemStack(aStack, true)));
        }

        /**
         * @return if this Fluid is a valid Input for any for the Recipes
         */
        public boolean containsInput(FluidStack aFluid) {
            return aFluid != null && containsInput(aFluid.getFluid());
        }

        /**
         * @return if this Fluid is a valid Input for any for the Recipes
         */
        public boolean containsInput(Fluid aFluid) {
            return aFluid != null && mRecipeFluidNameMap.contains(aFluid.getName());
        }

        @Nullable
        public final GT_Recipe findRecipe(IHasWorldObjectAndCoords aTileEntity, boolean aNotUnificated, long aVoltage,
            FluidStack[] aFluids, ItemStack... aInputs) {
            return findRecipe(aTileEntity, null, aNotUnificated, aVoltage, aFluids, null, aInputs);
        }

        @Nullable
        public final GT_Recipe findRecipe(IHasWorldObjectAndCoords aTileEntity, boolean aNotUnificated,
            boolean aDontCheckStackSizes, long aVoltage, FluidStack[] aFluids, ItemStack... aInputs) {
            return findRecipe(
                aTileEntity,
                null,
                aNotUnificated,
                aDontCheckStackSizes,
                aVoltage,
                aFluids,
                null,
                aInputs);
        }

        @Nullable
        public final GT_Recipe findRecipe(IHasWorldObjectAndCoords aTileEntity, GT_Recipe aRecipe,
            boolean aNotUnificated, long aVoltage, FluidStack[] aFluids, ItemStack... aInputs) {
            return findRecipe(aTileEntity, aRecipe, aNotUnificated, aVoltage, aFluids, null, aInputs);
        }

        @Nullable
        public final GT_Recipe findRecipe(IHasWorldObjectAndCoords aTileEntity, GT_Recipe aRecipe,
            boolean aNotUnificated, boolean aDontCheckStackSizes, long aVoltage, FluidStack[] aFluids,
            ItemStack... aInputs) {
            return findRecipe(
                aTileEntity,
                aRecipe,
                aNotUnificated,
                aDontCheckStackSizes,
                aVoltage,
                aFluids,
                null,
                aInputs);
        }

        @Nullable
        public final GT_Recipe findRecipe(IHasWorldObjectAndCoords aTileEntity, GT_Recipe aRecipe,
            boolean aNotUnificated, long aVoltage, FluidStack[] aFluids, ItemStack aSpecialSlot, ItemStack... aInputs) {
            return findRecipe(aTileEntity, aRecipe, aNotUnificated, false, aVoltage, aFluids, aSpecialSlot, aInputs);
        }

        // TODO: make this final after migrating BW
        @SuppressWarnings("unused")
        @Nullable
        public GT_Recipe findRecipe(IHasWorldObjectAndCoords aTileEntity, GT_Recipe aRecipe, boolean aNotUnificated,
            boolean aDontCheckStackSizes, long aVoltage, FluidStack[] aFluids, ItemStack aSpecialSlot,
            ItemStack... aInputs) {
            FindRecipeResult result = findRecipeWithResult(
                aRecipe,
                aNotUnificated,
                aDontCheckStackSizes,
                aVoltage,
                aFluids,
                aSpecialSlot,
                aInputs);
            return result.isSuccessful() ? result.getRecipe() : null;
        }

        /**
         * finds a Recipe matching the aFluid and ItemStack Inputs.
         *
         * @param aRecipe              in case this is != null it will try to use this Recipe first when looking things
         *                             up.
         * @param aNotUnificated       if this is T the Recipe searcher will unificate the ItemStack Inputs
         * @param aDontCheckStackSizes if set to false will only return recipes that can be executed at least once with
         *                             the provided input
         * @param aVoltage             Voltage of the Machine or Long.MAX_VALUE if it has no Voltage
         * @param aFluids              the Fluid Inputs
         * @param aSpecialSlot         the content of the Special Slot, the regular Manager doesn't do anything with
         *                             this, but some custom ones do.
         * @param aInputs              the Item Inputs
         * @return Result of the recipe search
         */
        @Nonnull
        public FindRecipeResult findRecipeWithResult(GT_Recipe aRecipe, boolean aNotUnificated,
            boolean aDontCheckStackSizes, long aVoltage, FluidStack[] aFluids, ItemStack aSpecialSlot,
            ItemStack... aInputs) {
            // No Recipes? Well, nothing to be found then.
            if (mRecipeList.isEmpty()) return NOT_FOUND;

            // Some Recipe Classes require a certain amount of Inputs of certain kinds. Like "at least 1 Fluid + 1
            // Stack" or "at least 2 Stacks" before they start searching for Recipes.
            // This improves Performance massively, especially if people leave things like Circuits, Molds or Shapes in
            // their Machines to select Sub Recipes.
            if (GregTech_API.sPostloadFinished) {
                if (mMinimalInputFluids > 0) {
                    if (aFluids == null) return NOT_FOUND;
                    int tAmount = 0;
                    for (FluidStack aFluid : aFluids) if (aFluid != null) tAmount++;
                    if (tAmount < mMinimalInputFluids) return NOT_FOUND;
                }
                if (mMinimalInputItems > 0) {
                    if (aInputs == null) return NOT_FOUND;
                    int tAmount = 0;
                    for (ItemStack aInput : aInputs) if (aInput != null) tAmount++;
                    if (tAmount < mMinimalInputItems) return NOT_FOUND;
                }
            }

            // Unification happens here in case the Input isn't already unificated.
            if (aNotUnificated) aInputs = GT_OreDictUnificator.getStackArray(true, (Object[]) aInputs);

            // Check the Recipe which has been used last time in order to not have to search for it again, if possible.
            if (aRecipe != null) if (!aRecipe.mFakeRecipe && aRecipe.mCanBeBuffered
                && aRecipe.isRecipeInputEqual(false, aDontCheckStackSizes, aFluids, aInputs)) {
                    if (!isSpecialSlotSensitive
                        || GT_Utility.areStacksEqualOrNull((ItemStack) aRecipe.mSpecialItems, aSpecialSlot)) {
                        return aRecipe.mEnabled && aVoltage * mAmperage >= aRecipe.mEUt
                            ? FindRecipeResult.ofSuccess(aRecipe)
                            : FindRecipeResult.ofInsufficientVoltage(aRecipe);
                    }
                }

            // Now look for the Recipes inside the Item HashMaps, but only when the Recipes usually have Items.
            if (mUsualInputCount > 0 && aInputs != null) for (ItemStack tStack : aInputs) if (tStack != null) {
                Collection<GT_Recipe> tRecipes = mRecipeItemMap.get(new GT_ItemStack(tStack));
                if (tRecipes != null) for (GT_Recipe tRecipe : tRecipes) if (!tRecipe.mFakeRecipe
                    && tRecipe.isRecipeInputEqual(false, aDontCheckStackSizes, aFluids, aInputs)) {
                        if (!isSpecialSlotSensitive
                            || GT_Utility.areStacksEqualOrNull((ItemStack) tRecipe.mSpecialItems, aSpecialSlot)) {
                            return tRecipe.mEnabled && aVoltage * mAmperage >= tRecipe.mEUt
                                ? FindRecipeResult.ofSuccess(tRecipe)
                                : FindRecipeResult.ofInsufficientVoltage(tRecipe);
                        }
                    }
                tRecipes = mRecipeItemMap.get(new GT_ItemStack(tStack, true));
                if (tRecipes != null) for (GT_Recipe tRecipe : tRecipes) if (!tRecipe.mFakeRecipe
                    && tRecipe.isRecipeInputEqual(false, aDontCheckStackSizes, aFluids, aInputs)) {
                        if (!isSpecialSlotSensitive
                            || GT_Utility.areStacksEqualOrNull((ItemStack) tRecipe.mSpecialItems, aSpecialSlot)) {
                            return tRecipe.mEnabled && aVoltage * mAmperage >= tRecipe.mEUt
                                ? FindRecipeResult.ofSuccess(tRecipe)
                                : FindRecipeResult.ofInsufficientVoltage(tRecipe);
                        }
                    }
            }

            // If the minimal Amount of Items for the Recipe is 0, then it could be a Fluid-Only Recipe, so check that
            // Map too.
            if (mMinimalInputItems == 0 && aFluids != null) for (FluidStack aFluid : aFluids) if (aFluid != null) {
                Collection<GT_Recipe> tRecipes = mRecipeFluidMap.get(
                    aFluid.getFluid()
                        .getName());
                if (tRecipes != null) for (GT_Recipe tRecipe : tRecipes) if (!tRecipe.mFakeRecipe
                    && tRecipe.isRecipeInputEqual(false, aDontCheckStackSizes, aFluids, aInputs)) {
                        if (!isSpecialSlotSensitive
                            || GT_Utility.areStacksEqualOrNull((ItemStack) tRecipe.mSpecialItems, aSpecialSlot)) {
                            return tRecipe.mEnabled && aVoltage * mAmperage >= tRecipe.mEUt
                                ? FindRecipeResult.ofSuccess(tRecipe)
                                : FindRecipeResult.ofInsufficientVoltage(tRecipe);
                        }
                    }
            }

            // And nothing has been found.
            return NOT_FOUND;
        }

        protected GT_Recipe addToItemMap(GT_Recipe aRecipe) {
            for (ItemStack aStack : aRecipe.mInputs) if (aStack != null) {
                GT_ItemStack tStack = new GT_ItemStack(aStack);
                Collection<GT_Recipe> tList = mRecipeItemMap.computeIfAbsent(tStack, k -> new HashSet<>(1));
                tList.add(aRecipe);
            }
            return aRecipe;
        }

        /**
         * Whether this recipe map contains any fluid outputs.
         */
        public boolean hasFluidOutputs() {
            return mHasFluidOutputs;
        }

        /**
         * Whether this recipe map contains any fluid inputs.
         */
        public boolean hasFluidInputs() {
            return mRecipeFluidNameMap.size() != 0;
        }

        /**
         * Whether this recipe map contains special slot inputs.
         */
        public boolean usesSpecialSlot() {
            return mUsesSpecialSlot;
        }

        public int getUsualFluidInputCount() {
            return Math.max(usualFluidInputCount, hasFluidInputs() ? 1 : 0);
        }

        public int getUsualFluidOutputCount() {
            return Math.max(usualFluidOutputCount, hasFluidOutputs() ? 1 : 0);
        }

        @Nullable
        public IDrawable getOverlayForSlot(boolean isFluid, boolean isOutput, int index, boolean isSpecial) {
            byte overlayKey = (byte) ((isFluid ? 1 : 0) + (isOutput ? 2 : 0)
                + (index == 0 ? 4 : 0)
                + (isSpecial ? 8 : 0));
            if (slotOverlays.containsKey(overlayKey)) {
                return slotOverlays.get(overlayKey);
            }
            return null;
        }

        @Nullable
        public SteamTexture getOverlayForSlotSteam(boolean isFluid, boolean isOutput, int index, boolean isSpecial) {
            byte overlayKey = (byte) ((isFluid ? 1 : 0) + (isOutput ? 2 : 0)
                + (index == 0 ? 4 : 0)
                + (isSpecial ? 8 : 0));
            if (slotOverlaysSteam.containsKey(overlayKey)) {
                return slotOverlaysSteam.get(overlayKey);
            }
            return null;
        }

        @Nullable
        public SteamTexture getOverlayForSlotSteam(boolean isOutput, boolean isFirst) {
            byte overlayKey = (byte) ((isOutput ? 2 : 0) + (isFirst ? 4 : 0));
            if (slotOverlaysSteam.containsKey(overlayKey)) {
                return slotOverlaysSteam.get(overlayKey);
            }
            return null;
        }

        public UITexture getProgressBarTexture() {
            return progressBarTexture.get();
        }

        public FallbackableUITexture getProgressBarTextureRaw() {
            return progressBarTexture;
        }

        public UITexture getProgressBarTextureSteam(SteamVariant steamVariant) {
            return progressBarTextureSteam.get(steamVariant);
        }

        public int getProgressBarImageSize() {
            if (progressBarImageSize != 0) {
                return progressBarImageSize;
            }
            return switch (progressBarDirection) {
                case UP, DOWN -> progressBarSize.height;
                case CIRCULAR_CW -> Math.max(progressBarSize.width, progressBarSize.height);
                default -> progressBarSize.width;
            };
        }

        /**
         * Adds slot backgrounds, progressBar, etc.
         */
        public ModularWindow.Builder createNEITemplate(IItemHandlerModifiable itemInputsInventory,
            IItemHandlerModifiable itemOutputsInventory, IItemHandlerModifiable specialSlotInventory,
            IItemHandlerModifiable fluidInputsInventory, IItemHandlerModifiable fluidOutputsInventory,
            Supplier<Float> progressSupplier, Pos2d windowOffset) {
            ModularWindow.Builder builder = ModularWindow.builder(neiBackgroundSize)
                .setBackground(ModularUITextures.VANILLA_BACKGROUND);

            UIHelper.forEachSlots(
                (i, backgrounds, pos) -> builder.widget(
                    SlotWidget.phantom(itemInputsInventory, i)
                        .setBackground(backgrounds)
                        .setPos(pos)
                        .setSize(18, 18)),
                (i, backgrounds, pos) -> builder.widget(
                    SlotWidget.phantom(itemOutputsInventory, i)
                        .setBackground(backgrounds)
                        .setPos(pos)
                        .setSize(18, 18)),
                (i, backgrounds, pos) -> {
                    if (usesSpecialSlot()) builder.widget(
                        SlotWidget.phantom(specialSlotInventory, 0)
                            .setBackground(backgrounds)
                            .setPos(pos)
                            .setSize(18, 18));
                },
                (i, backgrounds, pos) -> builder.widget(
                    SlotWidget.phantom(fluidInputsInventory, i)
                        .setBackground(backgrounds)
                        .setPos(pos)
                        .setSize(18, 18)),
                (i, backgrounds, pos) -> builder.widget(
                    SlotWidget.phantom(fluidOutputsInventory, i)
                        .setBackground(backgrounds)
                        .setPos(pos)
                        .setSize(18, 18)),
                ModularUITextures.ITEM_SLOT,
                ModularUITextures.FLUID_SLOT,
                this,
                mUsualInputCount,
                mUsualOutputCount,
                getUsualFluidInputCount(),
                getUsualFluidOutputCount(),
                SteamVariant.NONE,
                windowOffset);

            addProgressBarUI(builder, progressSupplier, windowOffset);
            addGregTechLogoUI(builder, windowOffset);

            for (Pair<IDrawable, Pair<Size, Pos2d>> specialTexture : specialTextures) {
                builder.widget(
                    new DrawableWidget().setDrawable(specialTexture.getLeft())
                        .setSize(
                            specialTexture.getRight()
                                .getLeft())
                        .setPos(
                            specialTexture.getRight()
                                .getRight()
                                .add(windowOffset)));
            }

            return builder;
        }

        public void addProgressBarUI(ModularWindow.Builder builder, Supplier<Float> progressSupplier,
            Pos2d windowOffset) {
            builder.widget(
                new ProgressBar().setTexture(getProgressBarTexture(), 20)
                    .setDirection(progressBarDirection)
                    .setProgress(progressSupplier)
                    .setSynced(false, false)
                    .setPos(progressBarPos.add(windowOffset))
                    .setSize(progressBarSize));
        }

        public void addGregTechLogoUI(ModularWindow.Builder builder, Pos2d windowOffset) {
            builder.widget(
                new DrawableWidget().setDrawable(logo)
                    .setSize(logoSize)
                    .setPos(logoPos.add(windowOffset)));
        }

        public void addRecipeSpecificDrawable(ModularWindow.Builder builder, Pos2d windowOffset,
            Supplier<IDrawable> supplier, Pos2d pos, Size size) {
            builder.widget(
                new DrawableWidget().setDrawable(supplier)
                    .setSize(size)
                    .setPos(pos.add(windowOffset)));
        }

        /**
         * Overriding this method allows custom NEI stack placement
         */
        public List<Pos2d> getItemInputPositions(int itemInputCount) {
            return UIHelper.getItemInputPositions(itemInputCount);
        }

        /**
         * Overriding this method allows custom NEI stack placement
         */
        public List<Pos2d> getItemOutputPositions(int itemOutputCount) {
            return UIHelper.getItemOutputPositions(itemOutputCount);
        }

        /**
         * Overriding this method allows custom NEI stack placement
         */
        public Pos2d getSpecialItemPosition() {
            return UIHelper.getSpecialItemPosition();
        }

        /**
         * Overriding this method allows custom NEI stack placement
         */
        public List<Pos2d> getFluidInputPositions(int fluidInputCount) {
            return UIHelper.getFluidInputPositions(fluidInputCount);
        }

        /**
         * Overriding this method allows custom NEI stack placement
         */
        public List<Pos2d> getFluidOutputPositions(int fluidOutputCount) {
            return UIHelper.getFluidOutputPositions(fluidOutputCount);
        }

        public void drawNEIDescription(NEIRecipeInfo recipeInfo) {
            drawNEIEnergyInfo(recipeInfo);
            drawNEIDurationInfo(recipeInfo);
            drawNEISpecialInfo(recipeInfo);
            drawNEIRecipeOwnerInfo(recipeInfo);
        }

        protected void drawNEIEnergyInfo(NEIRecipeInfo recipeInfo) {
            GT_Recipe recipe = recipeInfo.recipe;
            Power power = recipeInfo.power;
            if (power.getEuPerTick() > 0) {
                drawNEIText(recipeInfo, GT_Utility.trans("152", "Total: ") + power.getTotalPowerString());

                String amperage = power.getAmperageString();
                String powerUsage = power.getPowerUsageString();
                if (amperage == null || amperage.equals("unspecified") || powerUsage.contains("(OC)")) {
                    drawNEIText(recipeInfo, GT_Utility.trans("153", "Usage: ") + powerUsage);
                    if (GT_Mod.gregtechproxy.mNEIOriginalVoltage) {
                        Power originalPower = getPowerFromRecipeMap();
                        if (!(originalPower instanceof UnspecifiedEUPower)) {
                            originalPower.computePowerUsageAndDuration(recipe.mEUt, recipe.mDuration);
                            drawNEIText(
                                recipeInfo,
                                GT_Utility.trans("275", "Original voltage: ") + originalPower.getVoltageString());
                        }
                    }
                    if (amperage != null && !amperage.equals("unspecified") && !amperage.equals("1")) {
                        drawNEIText(recipeInfo, GT_Utility.trans("155", "Amperage: ") + amperage);
                    }
                } else if (amperage.equals("1")) {
                    drawNEIText(recipeInfo, GT_Utility.trans("154", "Voltage: ") + power.getVoltageString());
                } else {
                    drawNEIText(recipeInfo, GT_Utility.trans("153", "Usage: ") + powerUsage);
                    drawNEIText(recipeInfo, GT_Utility.trans("154", "Voltage: ") + power.getVoltageString());
                    drawNEIText(recipeInfo, GT_Utility.trans("155", "Amperage: ") + amperage);
                }
            }
        }

        protected void drawNEIDurationInfo(NEIRecipeInfo recipeInfo) {
            Power power = recipeInfo.power;
            if (power.getDurationTicks() > 0) {
                String textToDraw = GT_Utility.trans("158", "Time: ");
                if (GT_Mod.gregtechproxy.mNEIRecipeSecondMode) {
                    textToDraw += power.getDurationStringSeconds();
                    if (power.getDurationSeconds() <= 1.0d) {
                        textToDraw += String.format(" (%s)", power.getDurationStringTicks());
                    }
                } else {
                    textToDraw += power.getDurationStringTicks();
                }
                drawNEIText(recipeInfo, textToDraw);
            }
        }

        protected void drawNEISpecialInfo(NEIRecipeInfo recipeInfo) {
            String[] recipeDesc = recipeInfo.recipe.getNeiDesc();
            if (recipeDesc != null) {
                for (String s : recipeDesc) {
                    drawOptionalNEIText(recipeInfo, s);
                }
            } else if (neiSpecialInfoFormatter != null) {
                drawNEITextMultipleLines(
                    recipeInfo,
                    neiSpecialInfoFormatter.format(recipeInfo, this::formatSpecialValue));
            } else {
                drawOptionalNEIText(recipeInfo, getNEISpecialInfo(recipeInfo.recipe.mSpecialValue));
            }
        }

        protected String getNEISpecialInfo(int specialValue) {
            if (specialValue == -100 && GT_Mod.gregtechproxy.mLowGravProcessing) {
                return GT_Utility.trans("159", "Needs Low Gravity");
            } else if (specialValue == -200 && GT_Mod.gregtechproxy.mEnableCleanroom) {
                return GT_Utility.trans("160", "Needs Cleanroom");
            } else if (specialValue == -201) {
                return GT_Utility.trans("206", "Scan for Assembly Line");
            } else if (specialValue == -300 && GT_Mod.gregtechproxy.mEnableCleanroom) {
                return GT_Utility.trans("160.1", "Needs Cleanroom & LowGrav");
            } else if (specialValue == -400) {
                return GT_Utility.trans("216", "Deprecated Recipe");
            } else if (hasSpecialValueFormat()) {
                return formatSpecialValue(specialValue);
            }
            return null;
        }

        private boolean hasSpecialValueFormat() {
            return (GT_Utility.isStringValid(mNEISpecialValuePre)) || (GT_Utility.isStringValid(mNEISpecialValuePost));
        }

        protected String formatSpecialValue(int specialValue) {
            return mNEISpecialValuePre + formatNumbers((long) specialValue * mNEISpecialValueMultiplier)
                + mNEISpecialValuePost;
        }

        protected void drawNEIRecipeOwnerInfo(NEIRecipeInfo recipeInfo) {
            GT_Recipe recipe = recipeInfo.recipe;
            if (GT_Mod.gregtechproxy.mNEIRecipeOwner) {
                if (recipe.owners.size() > 1) {
                    drawNEIText(
                        recipeInfo,
                        EnumChatFormatting.ITALIC + GT_Utility.trans("273", "Original Recipe by: ")
                            + recipe.owners.get(0)
                                .getName());
                    for (int i = 1; i < recipe.owners.size(); i++) {
                        drawNEIText(
                            recipeInfo,
                            EnumChatFormatting.ITALIC + GT_Utility.trans("274", "Modified by: ")
                                + recipe.owners.get(i)
                                    .getName());
                    }
                } else if (recipe.owners.size() > 0) {
                    drawNEIText(
                        recipeInfo,
                        EnumChatFormatting.ITALIC + GT_Utility.trans("272", "Recipe by: ")
                            + recipe.owners.get(0)
                                .getName());
                }
            }
            if (GT_Mod.gregtechproxy.mNEIRecipeOwnerStackTrace && recipe.stackTraces != null
                && !recipe.stackTraces.isEmpty()) {
                drawNEIText(recipeInfo, "stackTrace:");
                // todo: good way to show all stacktraces
                for (StackTraceElement stackTrace : recipe.stackTraces.get(0)) {
                    drawNEIText(recipeInfo, stackTrace.toString());
                }
            }
        }

        protected void drawNEIText(NEIRecipeInfo recipeInfo, String text) {
            drawNEIText(recipeInfo, text, 10);
        }

        /**
         * Draws text on NEI recipe.
         *
         * @param yShift y position to shift after this text
         */
        @SuppressWarnings("SameParameterValue")
        protected void drawNEIText(NEIRecipeInfo recipeInfo, String text, int yShift) {
            drawNEIText(recipeInfo, text, 10, yShift);
        }

        /**
         * Draws text on NEI recipe.
         *
         * @param xStart x position to start drawing
         * @param yShift y position to shift after this text
         */
        @SuppressWarnings("SameParameterValue")
        protected void drawNEIText(NEIRecipeInfo recipeInfo, String text, int xStart, int yShift) {
            Minecraft.getMinecraft().fontRenderer.drawString(
                text,
                xStart,
                recipeInfo.yPos,
                neiTextColorOverride != -1 ? neiTextColorOverride : 0x000000);
            recipeInfo.yPos += yShift;
        }

        protected void drawOptionalNEIText(NEIRecipeInfo recipeInfo, String text) {
            if (GT_Utility.isStringValid(text) && !text.equals("unspecified")) {
                drawNEIText(recipeInfo, text, 10);
            }
        }

        protected void drawNEITextMultipleLines(NEIRecipeInfo recipeInfo, List<String> texts) {
            for (String text : texts) {
                drawNEIText(recipeInfo, text, 10);
            }
        }

        public List<String> handleNEIItemTooltip(ItemStack stack, List<String> currentTip,
            GT_NEI_DefaultHandler.CachedDefaultRecipe neiCachedRecipe) {
            for (PositionedStack pStack : neiCachedRecipe.mInputs) {
                if (stack == pStack.item) {
                    if (pStack instanceof GT_NEI_DefaultHandler.FixedPositionedStack) {
                        currentTip = handleNEIItemInputTooltip(
                            currentTip,
                            (GT_NEI_DefaultHandler.FixedPositionedStack) pStack);
                    }
                    break;
                }
            }
            for (PositionedStack pStack : neiCachedRecipe.mOutputs) {
                if (stack == pStack.item) {
                    if (pStack instanceof GT_NEI_DefaultHandler.FixedPositionedStack) {
                        currentTip = handleNEIItemOutputTooltip(
                            currentTip,
                            (GT_NEI_DefaultHandler.FixedPositionedStack) pStack);
                    }
                    break;
                }
            }
            return currentTip;
        }

        protected List<String> handleNEIItemInputTooltip(List<String> currentTip,
            GT_NEI_DefaultHandler.FixedPositionedStack pStack) {
            if (pStack.isNotConsumed()) {
                currentTip.add(GRAY + GT_Utility.trans("151", "Does not get consumed in the process"));
            }
            return currentTip;
        }

        protected List<String> handleNEIItemOutputTooltip(List<String> currentTip,
            GT_NEI_DefaultHandler.FixedPositionedStack pStack) {
            if (pStack.isChanceBased()) {
                currentTip.add(GRAY + GT_Utility.trans("150", "Chance: ") + pStack.getChanceText());
            }
            return currentTip;
        }

        public void drawNEIOverlays(GT_NEI_DefaultHandler.CachedDefaultRecipe neiCachedRecipe) {
            for (PositionedStack stack : neiCachedRecipe.mInputs) {
                if (stack instanceof GT_NEI_DefaultHandler.FixedPositionedStack) {
                    drawNEIOverlayForInput((GT_NEI_DefaultHandler.FixedPositionedStack) stack);
                }
            }
            for (PositionedStack stack : neiCachedRecipe.mOutputs) {
                if (stack instanceof GT_NEI_DefaultHandler.FixedPositionedStack) {
                    drawNEIOverlayForOutput((GT_NEI_DefaultHandler.FixedPositionedStack) stack);
                }
            }
        }

        protected void drawNEIOverlayForInput(GT_NEI_DefaultHandler.FixedPositionedStack stack) {
            if (stack.isNotConsumed()) {
                drawNEIOverlayText("NC", stack);
            }
        }

        protected void drawNEIOverlayForOutput(GT_NEI_DefaultHandler.FixedPositionedStack stack) {
            if (stack.isChanceBased()) {
                drawNEIOverlayText(stack.getChanceText(), stack);
            }
        }

        @SuppressWarnings("SameParameterValue")
        protected void drawNEIOverlayText(String text, PositionedStack stack, int color, float scale, boolean shadow,
            Alignment alignment) {
            FontRenderer fontRenderer = Minecraft.getMinecraft().fontRenderer;
            int width = fontRenderer.getStringWidth(text);
            int x = (int) ((stack.relx + 8 + 8 * alignment.x) / scale) - (width / 2 * (alignment.x + 1));
            int y = (int) ((stack.rely + 8 + 8 * alignment.y) / scale)
                - (fontRenderer.FONT_HEIGHT / 2 * (alignment.y + 1))
                - (alignment.y - 1) / 2;

            GlStateManager.pushMatrix();
            GlStateManager.scale(scale, scale, 1);
            fontRenderer.drawString(text, x, y, color, shadow);
            GlStateManager.popMatrix();
        }

        protected void drawNEIOverlayText(String text, PositionedStack stack) {
            drawNEIOverlayText(
                text,
                stack,
                colorOverride.getTextColorOrDefault("nei_overlay_yellow", 0xFDD835),
                0.5f,
                false,
                Alignment.TopLeft);
        }

        public void updateNEITextColorOverride() {
            neiTextColorOverride = colorOverride.getTextColorOrDefault("nei", -1);
        }

        public Power getPowerFromRecipeMap() {
            // By default, assume generic EU LV power with no overclocks
            Power power;
            if (mShowVoltageAmperageInNEI) {
                power = new EUPower((byte) 1, mAmperage);
            } else {
                power = new UnspecifiedEUPower((byte) 1, mAmperage);
            }
            return power;
        }

        /**
         * Use {@link #getItemInputPositions} or {@link #getSpecialItemPosition} or {@link #getFluidInputPositions}
         * instead
         */
        @Deprecated
        public ArrayList<PositionedStack> getInputPositionedStacks(GT_Recipe recipe) {
            return null;
        }

        /**
         * Use {@link #getItemOutputPositions} or {@link #getFluidOutputPositions} instead
         */
        @Deprecated
        public ArrayList<PositionedStack> getOutputPositionedStacks(GT_Recipe recipe) {
            return null;
        }

        public void addRecipe(Object o, FluidStack[] fluidInputArray, FluidStack[] fluidOutputArray) {}
    }

    // -----------------------------------------------------------------------------------------------------------------
    // Here are a few Classes I use for Special Cases in some Machines without having to write a separate Machine Class.
    // -----------------------------------------------------------------------------------------------------------------

    /**
     * Nicely display NEI with many items and fluids. Remember to call {@link GT_Recipe_Map#setUsualFluidInputCount} and
     * {@link GT_Recipe_Map#setUsualFluidOutputCount}. If row count >= 6, it doesn't fit in 2 recipes per page, so
     * change it via IMC.
     */
    public static class GT_Recipe_Map_LargeNEI extends GT_Recipe_Map {

        private static final int xDirMaxCount = 3;
        private static final int yOrigin = 8;

        public GT_Recipe_Map_LargeNEI(Collection<GT_Recipe> aRecipeList, String aUnlocalizedName, String aLocalName,
            String aNEIName, String aNEIGUIPath, int aUsualInputCount, int aUsualOutputCount, int aMinimalInputItems,
            int aMinimalInputFluids, int aAmperage, String aNEISpecialValuePre, int aNEISpecialValueMultiplier,
            String aNEISpecialValuePost, boolean aShowVoltageAmperageInNEI, boolean aNEIAllowed) {
            super(
                aRecipeList,
                aUnlocalizedName,
                aLocalName,
                aNEIName,
                aNEIGUIPath,
                aUsualInputCount,
                aUsualOutputCount,
                aMinimalInputItems,
                aMinimalInputFluids,
                aAmperage,
                aNEISpecialValuePre,
                aNEISpecialValueMultiplier,
                aNEISpecialValuePost,
                aShowVoltageAmperageInNEI,
                aNEIAllowed);
            useModularUI(true);
            setLogoPos(80, 62);
        }

        @Override
        public List<Pos2d> getItemInputPositions(int itemInputCount) {
            return UIHelper.getGridPositions(itemInputCount, 16, yOrigin, xDirMaxCount);
        }

        @Override
        public List<Pos2d> getItemOutputPositions(int itemOutputCount) {
            return UIHelper.getGridPositions(itemOutputCount, 106, yOrigin, xDirMaxCount);
        }

        @Override
        public List<Pos2d> getFluidInputPositions(int fluidInputCount) {
            return UIHelper.getGridPositions(fluidInputCount, 16, yOrigin + getItemRowCount() * 18, xDirMaxCount);
        }

        @Override
        public List<Pos2d> getFluidOutputPositions(int fluidOutputCount) {
            return UIHelper.getGridPositions(fluidOutputCount, 106, yOrigin + getItemRowCount() * 18, xDirMaxCount);
        }

        @Override
        public ModularWindow.Builder createNEITemplate(IItemHandlerModifiable itemInputsInventory,
            IItemHandlerModifiable itemOutputsInventory, IItemHandlerModifiable specialSlotInventory,
            IItemHandlerModifiable fluidInputsInventory, IItemHandlerModifiable fluidOutputsInventory,
            Supplier<Float> progressSupplier, Pos2d windowOffset) {
            // Delay setter so that calls to #setUsualFluidInputCount and #setUsualFluidOutputCount are considered
            setNEIBackgroundSize(172, 82 + (Math.max(getItemRowCount() + getFluidRowCount() - 4, 0)) * 18);
            return super.createNEITemplate(
                itemInputsInventory,
                itemOutputsInventory,
                specialSlotInventory,
                fluidInputsInventory,
                fluidOutputsInventory,
                progressSupplier,
                windowOffset);
        }

        private int getItemRowCount() {
            return (Math.max(mUsualInputCount, mUsualOutputCount) - 1) / xDirMaxCount + 1;
        }

        private int getFluidRowCount() {
            return (Math.max(getUsualFluidInputCount(), getUsualFluidOutputCount()) - 1) / xDirMaxCount + 1;
        }
    }

    /**
     * Display fluids where normally items are placed on NEI.
     */
    public static class GT_Recipe_Map_FluidOnly extends GT_Recipe_Map {

        public GT_Recipe_Map_FluidOnly(Collection<GT_Recipe> aRecipeList, String aUnlocalizedName, String aLocalName,
            String aNEIName, String aNEIGUIPath, int aUsualInputCount, int aUsualOutputCount, int aMinimalInputItems,
            int aMinimalInputFluids, int aAmperage, String aNEISpecialValuePre, int aNEISpecialValueMultiplier,
            String aNEISpecialValuePost, boolean aShowVoltageAmperageInNEI, boolean aNEIAllowed) {
            super(
                aRecipeList,
                aUnlocalizedName,
                aLocalName,
                aNEIName,
                aNEIGUIPath,
                aUsualInputCount,
                aUsualOutputCount,
                aMinimalInputItems,
                aMinimalInputFluids,
                aAmperage,
                aNEISpecialValuePre,
                aNEISpecialValueMultiplier,
                aNEISpecialValuePost,
                aShowVoltageAmperageInNEI,
                aNEIAllowed);
            useModularUI(true);
        }

        @Override
        public List<Pos2d> getFluidInputPositions(int fluidInputCount) {
            return UIHelper.getItemInputPositions(fluidInputCount);
        }

        @Override
        public List<Pos2d> getFluidOutputPositions(int fluidOutputCount) {
            return UIHelper.getItemOutputPositions(fluidOutputCount);
        }
    }

    /**
     * Abstract Class for general Recipe Handling of non GT Recipes
     */
    public abstract static class GT_Recipe_Map_NonGTRecipes extends GT_Recipe_Map {

        public GT_Recipe_Map_NonGTRecipes(Collection<GT_Recipe> aRecipeList, String aUnlocalizedName, String aLocalName,
            String aNEIName, String aNEIGUIPath, int aUsualInputCount, int aUsualOutputCount, int aMinimalInputItems,
            int aMinimalInputFluids, int aAmperage, String aNEISpecialValuePre, int aNEISpecialValueMultiplier,
            String aNEISpecialValuePost, boolean aShowVoltageAmperageInNEI, boolean aNEIAllowed) {
            super(
                aRecipeList,
                aUnlocalizedName,
                aLocalName,
                aNEIName,
                aNEIGUIPath,
                aUsualInputCount,
                aUsualOutputCount,
                aMinimalInputItems,
                aMinimalInputFluids,
                aAmperage,
                aNEISpecialValuePre,
                aNEISpecialValueMultiplier,
                aNEISpecialValuePost,
                aShowVoltageAmperageInNEI,
                aNEIAllowed);
        }

        @Override
        public boolean containsInput(ItemStack aStack) {
            return false;
        }

        @Override
        public boolean containsInput(FluidStack aFluid) {
            return false;
        }

        @Override
        public boolean containsInput(Fluid aFluid) {
            return false;
        }

        @Override
        public GT_Recipe addRecipe(boolean aOptimize, ItemStack[] aInputs, ItemStack[] aOutputs, Object aSpecial,
            int[] aOutputChances, FluidStack[] aFluidInputs, FluidStack[] aFluidOutputs, int aDuration, int aEUt,
            int aSpecialValue) {
            return null;
        }

        @Override
        public GT_Recipe addRecipe(boolean aOptimize, ItemStack[] aInputs, ItemStack[] aOutputs, Object aSpecial,
            FluidStack[] aFluidInputs, FluidStack[] aFluidOutputs, int aDuration, int aEUt, int aSpecialValue) {
            return null;
        }

        @Override
        public GT_Recipe addRecipe(GT_Recipe aRecipe) {
            return null;
        }

        @Override
        public GT_Recipe addFakeRecipe(boolean aCheckForCollisions, ItemStack[] aInputs, ItemStack[] aOutputs,
            Object aSpecial, int[] aOutputChances, FluidStack[] aFluidInputs, FluidStack[] aFluidOutputs, int aDuration,
            int aEUt, int aSpecialValue) {
            return null;
        }

        @Override
        public GT_Recipe addFakeRecipe(boolean aCheckForCollisions, ItemStack[] aInputs, ItemStack[] aOutputs,
            Object aSpecial, FluidStack[] aFluidInputs, FluidStack[] aFluidOutputs, int aDuration, int aEUt,
            int aSpecialValue) {
            return null;
        }

        @Override
        public GT_Recipe addFakeRecipe(boolean aCheckForCollisions, ItemStack[] aInputs, ItemStack[] aOutputs,
            Object aSpecial, FluidStack[] aFluidInputs, FluidStack[] aFluidOutputs, int aDuration, int aEUt,
            int aSpecialValue, boolean hidden) {
            return null;
        }

        @Override
        public GT_Recipe addFakeRecipe(boolean aCheckForCollisions, GT_Recipe aRecipe) {
            return null;
        }

        @Override
        public GT_Recipe add(GT_Recipe aRecipe) {
            return null;
        }

        @Override
        public void reInit() {
            /**/
        }

        @Override
        protected GT_Recipe addToItemMap(GT_Recipe aRecipe) {
            return null;
        }
    }

    /**
     * Just a Recipe Map with Utility specifically for Fuels.
     */
    public static class GT_Recipe_Map_Fuel extends GT_Recipe_Map {

        private final Map<String, GT_Recipe> mRecipesByFluidInput = new HashMap<>();

        public GT_Recipe_Map_Fuel(Collection<GT_Recipe> aRecipeList, String aUnlocalizedName, String aLocalName,
            String aNEIName, String aNEIGUIPath, int aUsualInputCount, int aUsualOutputCount, int aMinimalInputItems,
            int aMinimalInputFluids, int aAmperage, String aNEISpecialValuePre, int aNEISpecialValueMultiplier,
            String aNEISpecialValuePost, boolean aShowVoltageAmperageInNEI, boolean aNEIAllowed) {
            super(
                aRecipeList,
                aUnlocalizedName,
                aLocalName,
                aNEIName,
                aNEIGUIPath,
                aUsualInputCount,
                aUsualOutputCount,
                aMinimalInputItems,
                aMinimalInputFluids,
                aAmperage,
                aNEISpecialValuePre,
                aNEISpecialValueMultiplier,
                aNEISpecialValuePost,
                aShowVoltageAmperageInNEI,
                aNEIAllowed);
            setDisableOptimize(true);
        }

        public GT_Recipe addFuel(ItemStack aInput, ItemStack aOutput, int aFuelValueInEU) {
            return addFuel(aInput, aOutput, null, null, 10000, aFuelValueInEU);
        }

        public GT_Recipe addFuel(ItemStack aInput, ItemStack aOutput, int aChance, int aFuelValueInEU) {
            return addFuel(aInput, aOutput, null, null, aChance, aFuelValueInEU);
        }

        public GT_Recipe addFuel(FluidStack aFluidInput, FluidStack aFluidOutput, int aFuelValueInEU) {
            return addFuel(null, null, aFluidInput, aFluidOutput, 10000, aFuelValueInEU);
        }

        public GT_Recipe addFuel(ItemStack aInput, ItemStack aOutput, FluidStack aFluidInput, FluidStack aFluidOutput,
            int aFuelValueInEU) {
            return addFuel(aInput, aOutput, aFluidInput, aFluidOutput, 10000, aFuelValueInEU);
        }

        public GT_Recipe addFuel(ItemStack aInput, ItemStack aOutput, FluidStack aFluidInput, FluidStack aFluidOutput,
            int aChance, int aFuelValueInEU) {
            return addRecipe(
                true,
                new ItemStack[] { aInput },
                new ItemStack[] { aOutput },
                null,
                new int[] { aChance },
                new FluidStack[] { aFluidInput },
                new FluidStack[] { aFluidOutput },
                0,
                0,
                aFuelValueInEU);
        }

        @Override
        public GT_Recipe add(GT_Recipe aRecipe) {
            aRecipe = super.add(aRecipe);
            if (aRecipe.mInputs != null && GT_Utility.getNonnullElementCount(aRecipe.mInputs) == 1
                && (aRecipe.mFluidInputs == null || GT_Utility.getNonnullElementCount(aRecipe.mFluidInputs) == 0)) {
                FluidStack tFluid = GT_Utility.getFluidForFilledItem(aRecipe.mInputs[0], true);
                if (tFluid != null) {
                    tFluid.amount = 0;
                    mRecipesByFluidInput.put(tFluid.getUnlocalizedName(), aRecipe);
                }
            } else if ((aRecipe.mInputs == null || GT_Utility.getNonnullElementCount(aRecipe.mInputs) == 0)
                && aRecipe.mFluidInputs != null
                && GT_Utility.getNonnullElementCount(aRecipe.mFluidInputs) == 1
                && aRecipe.mFluidInputs[0] != null) {
                    mRecipesByFluidInput.put(aRecipe.mFluidInputs[0].getUnlocalizedName(), aRecipe);
                }
            return aRecipe;
        }

        public GT_Recipe findFuel(FluidStack aFluidInput) {
            return mRecipesByFluidInput.get(aFluidInput.getUnlocalizedName());
        }
    }

    /**
     * Special Class for Furnace Recipe handling.
     */
    public static class GT_Recipe_Map_Furnace extends GT_Recipe_Map_NonGTRecipes {

        public GT_Recipe_Map_Furnace(Collection<GT_Recipe> aRecipeList, String aUnlocalizedName, String aLocalName,
            String aNEIName, String aNEIGUIPath, int aUsualInputCount, int aUsualOutputCount, int aMinimalInputItems,
            int aMinimalInputFluids, int aAmperage, String aNEISpecialValuePre, int aNEISpecialValueMultiplier,
            String aNEISpecialValuePost, boolean aShowVoltageAmperageInNEI, boolean aNEIAllowed) {
            super(
                aRecipeList,
                aUnlocalizedName,
                aLocalName,
                aNEIName,
                aNEIGUIPath,
                aUsualInputCount,
                aUsualOutputCount,
                aMinimalInputItems,
                aMinimalInputFluids,
                aAmperage,
                aNEISpecialValuePre,
                aNEISpecialValueMultiplier,
                aNEISpecialValuePost,
                aShowVoltageAmperageInNEI,
                aNEIAllowed);
        }

        @Nonnull
        @Override
        public FindRecipeResult findRecipeWithResult(GT_Recipe aRecipe, boolean aNotUnificated,
            boolean aDontCheckStackSizes, long aVoltage, FluidStack[] aFluids, ItemStack aSpecialSlot,
            ItemStack... aInputs) {
            if (aInputs == null || aInputs.length == 0 || aInputs[0] == null) return NOT_FOUND;
            if (aRecipe != null && aRecipe.isRecipeInputEqual(false, true, aFluids, aInputs))
                return FindRecipeResult.ofSuccess(aRecipe);
            ItemStack tOutput = GT_ModHandler.getSmeltingOutput(aInputs[0], false, null);
            return tOutput == null ? NOT_FOUND
                : FindRecipeResult.ofSuccess(
                    new GT_Recipe(
                        false,
                        new ItemStack[] { GT_Utility.copyAmount(1, aInputs[0]) },
                        new ItemStack[] { tOutput },
                        null,
                        null,
                        null,
                        null,
                        128,
                        4,
                        0));
        }

        @Override
        public boolean containsInput(ItemStack aStack) {
            return GT_ModHandler.getSmeltingOutput(aStack, false, null) != null;
        }
    }

    /**
     * Special Class for Microwave Recipe handling.
     */
    public static class GT_Recipe_Map_Microwave extends GT_Recipe_Map_NonGTRecipes {

        public GT_Recipe_Map_Microwave(Collection<GT_Recipe> aRecipeList, String aUnlocalizedName, String aLocalName,
            String aNEIName, String aNEIGUIPath, int aUsualInputCount, int aUsualOutputCount, int aMinimalInputItems,
            int aMinimalInputFluids, int aAmperage, String aNEISpecialValuePre, int aNEISpecialValueMultiplier,
            String aNEISpecialValuePost, boolean aShowVoltageAmperageInNEI, boolean aNEIAllowed) {
            super(
                aRecipeList,
                aUnlocalizedName,
                aLocalName,
                aNEIName,
                aNEIGUIPath,
                aUsualInputCount,
                aUsualOutputCount,
                aMinimalInputItems,
                aMinimalInputFluids,
                aAmperage,
                aNEISpecialValuePre,
                aNEISpecialValueMultiplier,
                aNEISpecialValuePost,
                aShowVoltageAmperageInNEI,
                aNEIAllowed);
        }

        @Nonnull
        @Override
        public FindRecipeResult findRecipeWithResult(GT_Recipe aRecipe, boolean aNotUnificated,
            boolean aDontCheckStackSizes, long aVoltage, FluidStack[] aFluids, ItemStack aSpecialSlot,
            ItemStack... aInputs) {
            if (aInputs == null || aInputs.length == 0 || aInputs[0] == null) return NOT_FOUND;
            if (aRecipe != null && aRecipe.isRecipeInputEqual(false, true, aFluids, aInputs))
                return FindRecipeResult.ofSuccess(aRecipe);
            ItemStack tOutput = GT_ModHandler.getSmeltingOutput(aInputs[0], false, null);

            if (GT_Utility.areStacksEqual(aInputs[0], new ItemStack(Items.book, 1, W))) {
                return FindRecipeResult.ofSuccess(
                    new GT_Recipe(
                        false,
                        new ItemStack[] { GT_Utility.copyAmount(1, aInputs[0]) },
                        new ItemStack[] {
                            GT_Utility.getWrittenBook("Manual_Microwave", ItemList.Book_Written_03.get(1)) },
                        null,
                        null,
                        null,
                        null,
                        32,
                        4,
                        0));
            }

            // Check Container Item of Input since it is around the Input, then the Input itself, then Container Item of
            // Output and last check the Output itself
            for (ItemStack tStack : new ItemStack[] { GT_Utility.getContainerItem(aInputs[0], true), aInputs[0],
                GT_Utility.getContainerItem(tOutput, true), tOutput }) if (tStack != null) {
                    if (GT_Utility.areStacksEqual(tStack, new ItemStack(Blocks.netherrack, 1, W), true)
                        || GT_Utility.areStacksEqual(tStack, new ItemStack(Blocks.tnt, 1, W), true)
                        || GT_Utility.areStacksEqual(tStack, new ItemStack(Items.egg, 1, W), true)
                        || GT_Utility.areStacksEqual(tStack, new ItemStack(Items.firework_charge, 1, W), true)
                        || GT_Utility.areStacksEqual(tStack, new ItemStack(Items.fireworks, 1, W), true)
                        || GT_Utility.areStacksEqual(tStack, new ItemStack(Items.fire_charge, 1, W), true)) {
                        GT_Log.exp.println(
                            "Microwave Explosion due to TNT || EGG || FIREWORKCHARGE || FIREWORK || FIRE CHARGE");
                        return EXPLODE;
                    }
                    ItemData tData = GT_OreDictUnificator.getItemData(tStack);

                    if (tData != null) {
                        if (tData.mMaterial != null && tData.mMaterial.mMaterial != null) {
                            if (tData.mMaterial.mMaterial.contains(SubTag.METAL)
                                || tData.mMaterial.mMaterial.contains(SubTag.EXPLOSIVE)) {
                                GT_Log.exp.println("Microwave Explosion due to METAL insertion");
                                return EXPLODE;
                            }
                            if (tData.mMaterial.mMaterial.contains(SubTag.FLAMMABLE)) {
                                GT_Log.exp.println("Microwave INFLAMMATION due to FLAMMABLE insertion");
                                return ON_FIRE;
                            }
                        }
                        for (MaterialStack tMaterial : tData.mByProducts) if (tMaterial != null) {
                            if (tMaterial.mMaterial.contains(SubTag.METAL)
                                || tMaterial.mMaterial.contains(SubTag.EXPLOSIVE)) {
                                GT_Log.exp.println("Microwave Explosion due to METAL insertion");
                                return EXPLODE;
                            }
                            if (tMaterial.mMaterial.contains(SubTag.FLAMMABLE)) {
                                GT_Log.exp.println("Microwave INFLAMMATION due to FLAMMABLE insertion");
                                return ON_FIRE;
                            }
                        }
                    }
                    if (TileEntityFurnace.getItemBurnTime(tStack) > 0) {
                        GT_Log.exp.println("Microwave INFLAMMATION due to BURNABLE insertion");
                        return ON_FIRE;
                    }
                }

            return tOutput == null ? NOT_FOUND
                : FindRecipeResult.ofSuccess(
                    new GT_Recipe(
                        false,
                        new ItemStack[] { GT_Utility.copyAmount(1, aInputs[0]) },
                        new ItemStack[] { tOutput },
                        null,
                        null,
                        null,
                        null,
                        32,
                        4,
                        0));
        }

        @Override
        public boolean containsInput(ItemStack aStack) {
            return GT_ModHandler.getSmeltingOutput(aStack, false, null) != null;
        }
    }

    /**
     * Special Class for Unboxinator handling.
     */
    public static class GT_Recipe_Map_Unboxinator extends GT_Recipe_Map {

        public GT_Recipe_Map_Unboxinator(Collection<GT_Recipe> aRecipeList, String aUnlocalizedName, String aLocalName,
            String aNEIName, String aNEIGUIPath, int aUsualInputCount, int aUsualOutputCount, int aMinimalInputItems,
            int aMinimalInputFluids, int aAmperage, String aNEISpecialValuePre, int aNEISpecialValueMultiplier,
            String aNEISpecialValuePost, boolean aShowVoltageAmperageInNEI, boolean aNEIAllowed) {
            super(
                aRecipeList,
                aUnlocalizedName,
                aLocalName,
                aNEIName,
                aNEIGUIPath,
                aUsualInputCount,
                aUsualOutputCount,
                aMinimalInputItems,
                aMinimalInputFluids,
                aAmperage,
                aNEISpecialValuePre,
                aNEISpecialValueMultiplier,
                aNEISpecialValuePost,
                aShowVoltageAmperageInNEI,
                aNEIAllowed);
        }

        @Nonnull
        @Override
        public FindRecipeResult findRecipeWithResult(GT_Recipe aRecipe, boolean aNotUnificated,
            boolean aDontCheckStackSizes, long aVoltage, FluidStack[] aFluids, ItemStack aSpecialSlot,
            ItemStack... aInputs) {
            if (aInputs == null || aInputs.length == 0 || !ItemList.IC2_Scrapbox.isStackEqual(aInputs[0], false, true))
                return super.findRecipeWithResult(
                    aRecipe,
                    aNotUnificated,
                    aDontCheckStackSizes,
                    aVoltage,
                    aFluids,
                    aSpecialSlot,
                    aInputs);
            ItemStack tOutput = GT_ModHandler.getRandomScrapboxDrop();
            if (tOutput == null) return super.findRecipeWithResult(
                aRecipe,
                aNotUnificated,
                aDontCheckStackSizes,
                aVoltage,
                aFluids,
                aSpecialSlot,
                aInputs);
            GT_Recipe rRecipe = new GT_Recipe(
                false,
                new ItemStack[] { ItemList.IC2_Scrapbox.get(1) },
                new ItemStack[] { tOutput },
                null,
                null,
                null,
                null,
                16,
                1,
                0);
            // It is not allowed to be buffered due to the random Output
            rRecipe.mCanBeBuffered = false;
            // Due to its randomness it is not good if there are Items in the Output Slot, because those Items could
            // manipulate the outcome.
            rRecipe.mNeedsEmptyOutput = true;
            return FindRecipeResult.ofSuccess(rRecipe);
        }

        @Override
        public boolean containsInput(ItemStack aStack) {
            return ItemList.IC2_Scrapbox.isStackEqual(aStack, false, true) || super.containsInput(aStack);
        }
    }

    /**
     * Special Class for Fluid Canner handling.
     */
    public static class GT_Recipe_Map_FluidCanner extends GT_Recipe_Map {

        public GT_Recipe_Map_FluidCanner(Collection<GT_Recipe> aRecipeList, String aUnlocalizedName, String aLocalName,
            String aNEIName, String aNEIGUIPath, int aUsualInputCount, int aUsualOutputCount, int aMinimalInputItems,
            int aMinimalInputFluids, int aAmperage, String aNEISpecialValuePre, int aNEISpecialValueMultiplier,
            String aNEISpecialValuePost, boolean aShowVoltageAmperageInNEI, boolean aNEIAllowed) {
            super(
                aRecipeList,
                aUnlocalizedName,
                aLocalName,
                aNEIName,
                aNEIGUIPath,
                aUsualInputCount,
                aUsualOutputCount,
                aMinimalInputItems,
                aMinimalInputFluids,
                aAmperage,
                aNEISpecialValuePre,
                aNEISpecialValueMultiplier,
                aNEISpecialValuePost,
                aShowVoltageAmperageInNEI,
                aNEIAllowed);
        }

        @Nonnull
        @Override
        public FindRecipeResult findRecipeWithResult(GT_Recipe aRecipe, boolean aNotUnificated,
            boolean aDontCheckStackSizes, long aVoltage, FluidStack[] aFluids, ItemStack aSpecialSlot,
            ItemStack... aInputs) {
            FindRecipeResult result = super.findRecipeWithResult(
                aRecipe,
                aNotUnificated,
                aDontCheckStackSizes,
                aVoltage,
                aFluids,
                aSpecialSlot,
                aInputs);
            if (aInputs == null || aInputs.length == 0
                || aInputs[0] == null
                || result.isSuccessful()
                || !GregTech_API.sPostloadFinished) return result;

            if (aFluids != null && aFluids.length > 0 && aFluids[0] != null) {
                ItemStack tOutput = GT_Utility.fillFluidContainer(aFluids[0], aInputs[0], false, true);
                FluidStack tFluid = GT_Utility.getFluidForFilledItem(tOutput, true);
                if (tFluid != null) {
                    GT_Recipe recipe = new GT_Recipe(
                        false,
                        new ItemStack[] { GT_Utility.copyAmount(1, aInputs[0]) },
                        new ItemStack[] { tOutput },
                        null,
                        null,
                        new FluidStack[] { tFluid },
                        null,
                        Math.max(tFluid.amount / 64, 16),
                        1,
                        0);
                    recipe.mCanBeBuffered = false;
                    return FindRecipeResult.ofSuccess(recipe);
                }
            }
            FluidStack tFluid = GT_Utility.getFluidForFilledItem(aInputs[0], true);
            if (tFluid != null) {
                GT_Recipe recipe = new GT_Recipe(
                    false,
                    new ItemStack[] { GT_Utility.copyAmount(1, aInputs[0]) },
                    new ItemStack[] { GT_Utility.getContainerItem(aInputs[0], true) },
                    null,
                    null,
                    null,
                    new FluidStack[] { tFluid },
                    Math.max(tFluid.amount / 64, 16),
                    1,
                    0);
                recipe.mCanBeBuffered = false;
                return FindRecipeResult.ofSuccess(recipe);
            }
            return NOT_FOUND;
        }

        @Override
        public boolean containsInput(ItemStack aStack) {
            return aStack != null && (super.containsInput(aStack) || (aStack.getItem() instanceof IFluidContainerItem
                && ((IFluidContainerItem) aStack.getItem()).getCapacity(aStack) > 0));
        }

        @Override
        public boolean containsInput(FluidStack aFluid) {
            return true;
        }

        @Override
        public boolean containsInput(Fluid aFluid) {
            return true;
        }
    }

    /**
     * Special Class for Recycler Recipe handling.
     */
    public static class GT_Recipe_Map_Recycler extends GT_Recipe_Map_NonGTRecipes {

        public GT_Recipe_Map_Recycler(Collection<GT_Recipe> aRecipeList, String aUnlocalizedName, String aLocalName,
            String aNEIName, String aNEIGUIPath, int aUsualInputCount, int aUsualOutputCount, int aMinimalInputItems,
            int aMinimalInputFluids, int aAmperage, String aNEISpecialValuePre, int aNEISpecialValueMultiplier,
            String aNEISpecialValuePost, boolean aShowVoltageAmperageInNEI, boolean aNEIAllowed) {
            super(
                aRecipeList,
                aUnlocalizedName,
                aLocalName,
                aNEIName,
                aNEIGUIPath,
                aUsualInputCount,
                aUsualOutputCount,
                aMinimalInputItems,
                aMinimalInputFluids,
                aAmperage,
                aNEISpecialValuePre,
                aNEISpecialValueMultiplier,
                aNEISpecialValuePost,
                aShowVoltageAmperageInNEI,
                aNEIAllowed);
        }

        @Nonnull
        @Override
        public FindRecipeResult findRecipeWithResult(GT_Recipe aRecipe, boolean aNotUnificated,
            boolean aDontCheckStackSizes, long aVoltage, FluidStack[] aFluids, ItemStack aSpecialSlot,
            ItemStack... aInputs) {
            if (aInputs == null || aInputs.length == 0 || aInputs[0] == null) return NOT_FOUND;
            if (aRecipe != null && aRecipe.isRecipeInputEqual(false, true, aFluids, aInputs))
                return FindRecipeResult.ofSuccess(aRecipe);
            return FindRecipeResult.ofSuccess(
                new GT_Recipe(
                    false,
                    new ItemStack[] { GT_Utility.copyAmount(1, aInputs[0]) },
                    new ItemStack[] { GT_ModHandler.getRecyclerOutput(aInputs[0], 0) },
                    null,
                    new int[] { 1250 },
                    null,
                    null,
                    45,
                    1,
                    0));
        }

        @Override
        public boolean containsInput(ItemStack aStack) {
            return GT_ModHandler.getRecyclerOutput(aStack, 0) != null;
        }
    }

    /**
     * Special Class for Macerator/RockCrusher Recipe handling.
     */
    public static class GT_Recipe_Map_Macerator extends GT_Recipe_Map {

        public GT_Recipe_Map_Macerator(Collection<GT_Recipe> aRecipeList, String aUnlocalizedName, String aLocalName,
            String aNEIName, String aNEIGUIPath, int aUsualInputCount, int aUsualOutputCount, int aMinimalInputItems,
            int aMinimalInputFluids, int aAmperage, String aNEISpecialValuePre, int aNEISpecialValueMultiplier,
            String aNEISpecialValuePost, boolean aShowVoltageAmperageInNEI, boolean aNEIAllowed) {
            super(
                aRecipeList,
                aUnlocalizedName,
                aLocalName,
                aNEIName,
                aNEIGUIPath,
                aUsualInputCount,
                aUsualOutputCount,
                aMinimalInputItems,
                aMinimalInputFluids,
                aAmperage,
                aNEISpecialValuePre,
                aNEISpecialValueMultiplier,
                aNEISpecialValuePost,
                aShowVoltageAmperageInNEI,
                aNEIAllowed);
        }

        @Nonnull
        @Override
        public FindRecipeResult findRecipeWithResult(GT_Recipe aRecipe, boolean aNotUnificated,
            boolean aDontCheckStackSizes, long aVoltage, FluidStack[] aFluids, ItemStack aSpecialSlot,
            ItemStack... aInputs) {
            if (aInputs == null || aInputs.length == 0 || aInputs[0] == null || !GregTech_API.sPostloadFinished)
                return super.findRecipeWithResult(
                    aRecipe,
                    aNotUnificated,
                    aDontCheckStackSizes,
                    aVoltage,
                    aFluids,
                    aSpecialSlot,
                    aInputs);
            FindRecipeResult result = super.findRecipeWithResult(
                aRecipe,
                aNotUnificated,
                aDontCheckStackSizes,
                aVoltage,
                aFluids,
                aSpecialSlot,
                aInputs);
            if (result.isSuccessful()) return result;

            try {
                List<ItemStack> tRecipeOutputs = mods.railcraft.api.crafting.RailcraftCraftingManager.rockCrusher
                    .getRecipe(GT_Utility.copyAmount(1, aInputs[0]))
                    .getRandomizedOuputs();
                if (tRecipeOutputs != null) {
                    GT_Recipe recipe = new GT_Recipe(
                        false,
                        new ItemStack[] { GT_Utility.copyAmount(1, aInputs[0]) },
                        tRecipeOutputs.toArray(new ItemStack[0]),
                        null,
                        null,
                        null,
                        null,
                        800,
                        2,
                        0);
                    recipe.mCanBeBuffered = false;
                    recipe.mNeedsEmptyOutput = true;
                    return FindRecipeResult.ofSuccess(recipe);
                }
            } catch (NoClassDefFoundError e) {
                if (D1) GT_Log.err.println("Railcraft Not loaded");
            } catch (NullPointerException e) {
                /**/
            }

            ItemStack tComparedInput = GT_Utility.copyOrNull(aInputs[0]);
            ItemStack[] tOutputItems = GT_ModHandler.getMachineOutput(
                tComparedInput,
                ic2.api.recipe.Recipes.macerator.getRecipes(),
                true,
                new NBTTagCompound(),
                null,
                null,
                null);
            if (tComparedInput != null && GT_Utility.arrayContainsNonNull(tOutputItems)) {
                return FindRecipeResult.ofSuccess(
                    new GT_Recipe(
                        false,
                        new ItemStack[] {
                            GT_Utility.copyAmount(aInputs[0].stackSize - tComparedInput.stackSize, aInputs[0]) },
                        tOutputItems,
                        null,
                        null,
                        null,
                        null,
                        400,
                        2,
                        0));
            }
            return NOT_FOUND;
        }

        @Override
        public boolean containsInput(ItemStack aStack) {
            return super.containsInput(aStack) || GT_Utility.arrayContainsNonNull(
                GT_ModHandler.getMachineOutput(
                    GT_Utility.copyAmount(64, aStack),
                    ic2.api.recipe.Recipes.macerator.getRecipes(),
                    false,
                    new NBTTagCompound(),
                    null,
                    null,
                    null));
        }
    }

    /**
     * Special Class for Assembler handling.
     */
    public static class GT_Recipe_Map_Assembler extends GT_Recipe_Map {

        public GT_Recipe_Map_Assembler(Collection<GT_Recipe> aRecipeList, String aUnlocalizedName, String aLocalName,
            String aNEIName, String aNEIGUIPath, int aUsualInputCount, int aUsualOutputCount, int aMinimalInputItems,
            int aMinimalInputFluids, int aAmperage, String aNEISpecialValuePre, int aNEISpecialValueMultiplier,
            String aNEISpecialValuePost, boolean aShowVoltageAmperageInNEI, boolean aNEIAllowed) {
            super(
                aRecipeList,
                aUnlocalizedName,
                aLocalName,
                aNEIName,
                aNEIGUIPath,
                aUsualInputCount,
                aUsualOutputCount,
                aMinimalInputItems,
                aMinimalInputFluids,
                aAmperage,
                aNEISpecialValuePre,
                aNEISpecialValueMultiplier,
                aNEISpecialValuePost,
                aShowVoltageAmperageInNEI,
                aNEIAllowed);
        }

        @Nonnull
        @Override
        public FindRecipeResult findRecipeWithResult(GT_Recipe aRecipe, boolean aNotUnificated,
            boolean aDontCheckStackSizes, long aVoltage, FluidStack[] aFluids, ItemStack aSpecialSlot,
            ItemStack... aInputs) {

            FindRecipeResult result = super.findRecipeWithResult(
                aRecipe,
                true,
                aDontCheckStackSizes,
                aVoltage,
                aFluids,
                aSpecialSlot,
                aInputs);
            /*
             * Doesnt work, keep it as a reminder tho if (rRecipe == null){ Set<ItemStack> aInputs2 = new
             * TreeSet<ItemStack>(); for (ItemStack aInput : aInputs) { aInputs2.add(aInput); } for (ItemStack aInput :
             * aInputs) { aInputs2.remove(aInput); int[] oredictIDs = OreDictionary.getOreIDs(aInput); if (
             * oredictIDs.length > 1){ for (final int i : oredictIDs){ final ItemStack[] oredictIS = (ItemStack[])
             * OreDictionary.getOres(OreDictionary.getOreName(i)).toArray(); if (oredictIS != null && oredictIS.length >
             * 1){ for (final ItemStack IS : oredictIS){ aInputs2.add(IS); ItemStack[] temp = (ItemStack[])
             * aInputs2.toArray(); rRecipe = super.findRecipe(aTileEntity, aRecipe, aNotUnificated, aVoltage, aFluids,
             * aSpecialSlot,temp); if(rRecipe!= null){ break; } else { aInputs2.remove(IS); } } if(rRecipe!= null)
             * break; } } if(rRecipe!= null) break; }else aInputs2.add(aInput); if(rRecipe!= null) break; } }
             */
            if (aInputs == null || aInputs.length == 0
                || aInputs[0] == null
                || !result.isSuccessful()
                || !GregTech_API.sPostloadFinished) return result;

            GT_Recipe rRecipe = result.getRecipeNonNull();
            for (ItemStack aInput : aInputs) {
                if (ItemList.Paper_Printed_Pages.isStackEqual(aInput, false, true)) {
                    rRecipe = rRecipe.copy();
                    rRecipe.mCanBeBuffered = false;
                    rRecipe.mOutputs[0].setTagCompound(aInput.getTagCompound());
                }
            }
            return FindRecipeResult.ofSuccess(rRecipe);
        }
    }

    /**
     * Special Class for Forming Press handling.
     */
    public static class GT_Recipe_Map_FormingPress extends GT_Recipe_Map {

        public GT_Recipe_Map_FormingPress(Collection<GT_Recipe> aRecipeList, String aUnlocalizedName, String aLocalName,
            String aNEIName, String aNEIGUIPath, int aUsualInputCount, int aUsualOutputCount, int aMinimalInputItems,
            int aMinimalInputFluids, int aAmperage, String aNEISpecialValuePre, int aNEISpecialValueMultiplier,
            String aNEISpecialValuePost, boolean aShowVoltageAmperageInNEI, boolean aNEIAllowed) {
            super(
                aRecipeList,
                aUnlocalizedName,
                aLocalName,
                aNEIName,
                aNEIGUIPath,
                aUsualInputCount,
                aUsualOutputCount,
                aMinimalInputItems,
                aMinimalInputFluids,
                aAmperage,
                aNEISpecialValuePre,
                aNEISpecialValueMultiplier,
                aNEISpecialValuePost,
                aShowVoltageAmperageInNEI,
                aNEIAllowed);
        }

        @Nonnull
        @Override
        public FindRecipeResult findRecipeWithResult(GT_Recipe aRecipe, boolean aNotUnificated,
            boolean aDontCheckStackSizes, long aVoltage, FluidStack[] aFluids, ItemStack aSpecialSlot,
            ItemStack... aInputs) {
            FindRecipeResult result = super.findRecipeWithResult(
                aRecipe,
                aNotUnificated,
                aDontCheckStackSizes,
                aVoltage,
                aFluids,
                aSpecialSlot,
                aInputs);
            if (aInputs == null || aInputs.length < 2 || !GregTech_API.sPostloadFinished) return result;
            if (!result.isSuccessful()) {
                return findRenamingRecipe(aInputs);
            }
            for (ItemStack aMold : aInputs) {
                if (ItemList.Shape_Mold_Credit.isStackEqual(aMold, false, true)) {
                    NBTTagCompound tNBT = aMold.getTagCompound();
                    if (tNBT == null) tNBT = new NBTTagCompound();
                    if (!tNBT.hasKey("credit_security_id")) tNBT.setLong("credit_security_id", System.nanoTime());
                    aMold.setTagCompound(tNBT);

                    GT_Recipe rRecipe = result.getRecipeNonNull();
                    rRecipe = rRecipe.copy();
                    rRecipe.mCanBeBuffered = false;
                    rRecipe.mOutputs[0].setTagCompound(tNBT);
                    return FindRecipeResult.ofSuccess(rRecipe);
                }
            }
            return result;
        }

        private ItemStack findNameMoldIndex(ItemStack[] inputs) {
            for (ItemStack stack : inputs) {
                if (ItemList.Shape_Mold_Name.isStackEqual(stack, false, true)) return stack;
            }
            return null;
        }

        private ItemStack findStackToRename(ItemStack[] inputs, ItemStack mold) {
            for (ItemStack stack : inputs) {
                if (stack == mold || stack == null) continue;
                return stack;
            }
            return null;
        }

        @Nonnull
        private FindRecipeResult findRenamingRecipe(ItemStack[] inputs) {
            ItemStack mold = findNameMoldIndex(inputs);
            if (mold == null) return NOT_FOUND;
            ItemStack input = findStackToRename(inputs, mold);
            if (input == null) return NOT_FOUND;
            ItemStack output = GT_Utility.copyAmount(1, input);
            if (output == null) return NOT_FOUND;
            output.setStackDisplayName(mold.getDisplayName());
            GT_Recipe recipe = new GT_Recipe(
                false,
                new ItemStack[] { ItemList.Shape_Mold_Name.get(0), GT_Utility.copyAmount(1, input) },
                new ItemStack[] { output },
                null,
                null,
                null,
                null,
                128,
                8,
                0);
            recipe.mCanBeBuffered = false;
            return FindRecipeResult.ofSuccess(recipe);
        }
    }

    /**
     * Special Class for Printer handling.
     */
    public static class GT_Recipe_Map_Printer extends GT_Recipe_Map {

        public GT_Recipe_Map_Printer(Collection<GT_Recipe> aRecipeList, String aUnlocalizedName, String aLocalName,
            String aNEIName, String aNEIGUIPath, int aUsualInputCount, int aUsualOutputCount, int aMinimalInputItems,
            int aMinimalInputFluids, int aAmperage, String aNEISpecialValuePre, int aNEISpecialValueMultiplier,
            String aNEISpecialValuePost, boolean aShowVoltageAmperageInNEI, boolean aNEIAllowed) {
            super(
                aRecipeList,
                aUnlocalizedName,
                aLocalName,
                aNEIName,
                aNEIGUIPath,
                aUsualInputCount,
                aUsualOutputCount,
                aMinimalInputItems,
                aMinimalInputFluids,
                aAmperage,
                aNEISpecialValuePre,
                aNEISpecialValueMultiplier,
                aNEISpecialValuePost,
                aShowVoltageAmperageInNEI,
                aNEIAllowed);
        }

        @Nonnull
        @Override
        public FindRecipeResult findRecipeWithResult(GT_Recipe aRecipe, boolean aNotUnificated,
            boolean aDontCheckStackSizes, long aVoltage, FluidStack[] aFluids, ItemStack aSpecialSlot,
            ItemStack... aInputs) {
            FindRecipeResult result = super.findRecipeWithResult(
                aRecipe,
                aNotUnificated,
                aDontCheckStackSizes,
                aVoltage,
                aFluids,
                aSpecialSlot,
                aInputs);
            if (aInputs == null || aInputs.length == 0
                || aInputs[0] == null
                || aFluids == null
                || aFluids.length == 0
                || aFluids[0] == null
                || !GregTech_API.sPostloadFinished) return result;

            Dyes aDye = null;
            for (Dyes tDye : Dyes.VALUES) if (tDye.isFluidDye(aFluids[0])) {
                aDye = tDye;
                break;
            }

            if (aDye == null) return result;

            if (!result.isSuccessful()) {
                ItemStack tOutput = GT_ModHandler.getAllRecipeOutput(
                    null,
                    aInputs[0],
                    aInputs[0],
                    aInputs[0],
                    aInputs[0],
                    ItemList.DYE_ONLY_ITEMS[aDye.mIndex].get(1),
                    aInputs[0],
                    aInputs[0],
                    aInputs[0],
                    aInputs[0]);
                if (tOutput != null) {
                    GT_Recipe recipe = addRecipe(
                        new GT_Recipe(
                            true,
                            new ItemStack[] { GT_Utility.copyAmount(8, aInputs[0]) },
                            new ItemStack[] { tOutput },
                            null,
                            null,
                            new FluidStack[] { new FluidStack(aFluids[0].getFluid(), (int) L) },
                            null,
                            256,
                            2,
                            0),
                        false,
                        false,
                        true);
                    return recipe != null ? FindRecipeResult.ofSuccess(recipe) : NOT_FOUND;
                }

                tOutput = GT_ModHandler
                    .getAllRecipeOutput(null, aInputs[0], ItemList.DYE_ONLY_ITEMS[aDye.mIndex].get(1));
                if (tOutput != null) {
                    GT_Recipe recipe = addRecipe(
                        new GT_Recipe(
                            true,
                            new ItemStack[] { GT_Utility.copyAmount(1, aInputs[0]) },
                            new ItemStack[] { tOutput },
                            null,
                            null,
                            new FluidStack[] { new FluidStack(aFluids[0].getFluid(), (int) L) },
                            null,
                            32,
                            2,
                            0),
                        false,
                        false,
                        true);
                    return recipe != null ? FindRecipeResult.ofSuccess(recipe) : NOT_FOUND;
                }
            } else {
                GT_Recipe rRecipe = result.getRecipeNonNull();
                if (aInputs[0].getItem() == Items.paper) {
                    if (!ItemList.Tool_DataStick.isStackEqual(aSpecialSlot, false, true)) return NOT_FOUND;
                    NBTTagCompound tNBT = aSpecialSlot.getTagCompound();
                    if (tNBT == null || GT_Utility.isStringInvalid(tNBT.getString("title"))
                        || GT_Utility.isStringInvalid(tNBT.getString("author"))) return NOT_FOUND;

                    rRecipe = rRecipe.copy();
                    rRecipe.mCanBeBuffered = false;
                    rRecipe.mOutputs[0].setTagCompound(tNBT);
                    return FindRecipeResult.ofSuccess(rRecipe);
                }
                if (aInputs[0].getItem() == Items.map) {
                    if (!ItemList.Tool_DataStick.isStackEqual(aSpecialSlot, false, true)) return NOT_FOUND;
                    NBTTagCompound tNBT = aSpecialSlot.getTagCompound();
                    if (tNBT == null || !tNBT.hasKey("map_id")) return NOT_FOUND;

                    rRecipe = rRecipe.copy();
                    rRecipe.mCanBeBuffered = false;
                    rRecipe.mOutputs[0].setItemDamage(tNBT.getShort("map_id"));
                    return FindRecipeResult.ofSuccess(rRecipe);
                }
                if (ItemList.Paper_Punch_Card_Empty.isStackEqual(aInputs[0], false, true)) {
                    if (!ItemList.Tool_DataStick.isStackEqual(aSpecialSlot, false, true)) return NOT_FOUND;
                    NBTTagCompound tNBT = aSpecialSlot.getTagCompound();
                    if (tNBT == null || !tNBT.hasKey("GT.PunchCardData")) return NOT_FOUND;

                    rRecipe = rRecipe.copy();
                    rRecipe.mCanBeBuffered = false;
                    rRecipe.mOutputs[0].setTagCompound(
                        GT_Utility.getNBTContainingString(
                            new NBTTagCompound(),
                            "GT.PunchCardData",
                            tNBT.getString("GT.PunchCardData")));
                    return FindRecipeResult.ofSuccess(rRecipe);
                }
            }
            return result;
        }

        @Override
        public boolean containsInput(ItemStack aStack) {
            return true;
        }

        @Override
        public boolean containsInput(FluidStack aFluid) {
            return super.containsInput(aFluid) || Dyes.isAnyFluidDye(aFluid);
        }

        @Override
        public boolean containsInput(Fluid aFluid) {
            return super.containsInput(aFluid) || Dyes.isAnyFluidDye(aFluid);
        }
    }

    public static class GT_Recipe_Map_LargeBoilerFakeFuels extends GT_Recipe_Map {

        private static final List<String> ALLOWED_SOLID_FUELS = Arrays.asList(
            GregTech_API.sMachineFile.mConfig.getStringList(
                "LargeBoiler.allowedFuels",
                ConfigCategories.machineconfig.toString(),
                new String[] { "gregtech:gt.blockreinforced:6", "gregtech:gt.blockreinforced:7" },
                "Allowed fuels for the Large Titanium Boiler and Large Tungstensteel Boiler"));

        public GT_Recipe_Map_LargeBoilerFakeFuels() {
            super(
                new HashSet<>(55),
                "gt.recipe.largeboilerfakefuels",
                "Large Boiler",
                null,
                GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Default"),
                1,
                1,
                1,
                0,
                1,
                E,
                1,
                E,
                true,
                true);
            GT_Recipe explanatoryRecipe = new GT_Recipe(
                true,
                new ItemStack[] {},
                new ItemStack[] {},
                null,
                null,
                null,
                null,
                1,
                1,
                1);
            explanatoryRecipe.setNeiDesc(
                "Not all solid fuels are listed.",
                "Any item that burns in a",
                "vanilla furnace will burn in",
                "a Large Bronze or Steel Boiler.");
            addRecipe(explanatoryRecipe);
        }

        public static boolean isAllowedSolidFuel(ItemStack stack) {
            return isAllowedSolidFuel(Item.itemRegistry.getNameForObject(stack.getItem()), stack.getItemDamage());
        }

        public static boolean isAllowedSolidFuel(String itemRegistryName, int meta) {
            return ALLOWED_SOLID_FUELS.contains(itemRegistryName + ":" + meta);
        }

        public static boolean addAllowedSolidFuel(ItemStack stack) {
            return addAllowedSolidFuel(Item.itemRegistry.getNameForObject(stack.getItem()), stack.getItemDamage());
        }

        public static boolean addAllowedSolidFuel(String itemregistryName, int meta) {
            return ALLOWED_SOLID_FUELS.add(itemregistryName + ":" + meta);
        }

        public GT_Recipe addDenseLiquidRecipe(GT_Recipe recipe) {
            return addRecipe(recipe, ((double) recipe.mSpecialValue) / 10);
        }

        public GT_Recipe addDieselRecipe(GT_Recipe recipe) {
            return addRecipe(recipe, ((double) recipe.mSpecialValue) / 40);
        }

        public void addSolidRecipes(ItemStack... itemStacks) {
            for (ItemStack itemStack : itemStacks) {
                addSolidRecipe(itemStack);
            }
        }

        public GT_Recipe addSolidRecipe(ItemStack fuelItemStack) {
            boolean allowedFuel = false;
            if (fuelItemStack != null) {
                String registryName = Item.itemRegistry.getNameForObject(fuelItemStack.getItem());
                allowedFuel = ALLOWED_SOLID_FUELS.contains(registryName + ":" + fuelItemStack.getItemDamage());
            }
            return addRecipe(
                new GT_Recipe(
                    true,
                    new ItemStack[] { fuelItemStack },
                    new ItemStack[] {},
                    null,
                    null,
                    null,
                    null,
                    1,
                    0,
                    GT_ModHandler.getFuelValue(fuelItemStack) / 1600),
                ((double) GT_ModHandler.getFuelValue(fuelItemStack)) / 1600,
                allowedFuel);
        }

        private GT_Recipe addRecipe(GT_Recipe recipe, double baseBurnTime, boolean isAllowedFuel) {
            recipe = new GT_Recipe(recipe, true);
            // Some recipes will have a burn time like 15.9999999 and % always rounds down
            double floatErrorCorrection = 0.0001;

            double bronzeBurnTime = baseBurnTime * 2 + floatErrorCorrection;
            bronzeBurnTime -= bronzeBurnTime % 0.05;
            double steelBurnTime = baseBurnTime + floatErrorCorrection;
            steelBurnTime -= steelBurnTime % 0.05;
            double titaniumBurnTime = baseBurnTime * 0.3 + floatErrorCorrection;
            titaniumBurnTime -= titaniumBurnTime % 0.05;
            double tungstensteelBurnTime = baseBurnTime * 0.15 + floatErrorCorrection;
            tungstensteelBurnTime -= tungstensteelBurnTime % 0.05;

            if (isAllowedFuel) {
                recipe.setNeiDesc(
                    "Burn time in seconds:",
                    String.format("Bronze Boiler: %.4f", bronzeBurnTime),
                    String.format("Steel Boiler: %.4f", steelBurnTime),
                    String.format("Titanium Boiler: %.4f", titaniumBurnTime),
                    String.format("Tungstensteel Boiler: %.4f", tungstensteelBurnTime));
            } else {
                recipe.setNeiDesc(
                    "Burn time in seconds:",
                    String.format("Bronze Boiler: %.4f", bronzeBurnTime),
                    String.format("Steel Boiler: %.4f", steelBurnTime),
                    "Titanium Boiler: Not allowed",
                    "Tungstenst. Boiler: Not allowed");
            }

            return super.addRecipe(recipe);
        }

        private GT_Recipe addRecipe(GT_Recipe recipe, double baseBurnTime) {
            recipe = new GT_Recipe(recipe, true);
            // Some recipes will have a burn time like 15.9999999 and % always rounds down
            double floatErrorCorrection = 0.0001;

            double bronzeBurnTime = baseBurnTime * 2 + floatErrorCorrection;
            bronzeBurnTime -= bronzeBurnTime % 0.05;
            double steelBurnTime = baseBurnTime + floatErrorCorrection;
            steelBurnTime -= steelBurnTime % 0.05;

            recipe.setNeiDesc(
                "Burn time in seconds:",
                String.format("Bronze Boiler: %.4f", bronzeBurnTime),
                String.format("Steel Boiler: %.4f", steelBurnTime),
                "Titanium Boiler: Not allowed",
                "Tungstenst. Boiler: Not allowed");

            return super.addRecipe(recipe);
        }
    }

    public static class GT_Recipe_Map_IC2NuclearFake extends GT_Recipe_Map {

        public GT_Recipe_Map_IC2NuclearFake() {
            super(
                new HashSet<>(10),
                "gt.recipe.ic2nuke",
                "Fission",
                null,
                GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "Default"),
                1,
                1,
                1,
                0,
                1,
                E,
                1,
                E,
                true,
                true);
            setLogo(GT_UITextures.PICTURE_RADIATION_WARNING);
            setLogoPos(152, 24);
            setNEIBackgroundSize(172, 60);
            setProgressBar(GT_UITextures.PROGRESSBAR_ARROW, ProgressBar.Direction.RIGHT);
        }

        /**
         * Add a breeder cell.
         *
         * @param input          raw stack. should be undamaged.
         * @param output         breed output
         * @param heatMultiplier bonus progress per neutron pulse per heat step
         * @param heatStep       divisor for hull heat
         * @param reflector      true if also acts as a neutron reflector, false otherwise.
         * @param requiredPulses progress required to complete breeding
         * @return added fake recipe
         */
        public GT_Recipe addBreederCell(ItemStack input, ItemStack output, boolean reflector, int heatStep,
            int heatMultiplier, int requiredPulses) {
            return addFakeRecipe(
                input,
                output,
                reflector ? "Neutron reflecting breeder cell" : "Heat neutral Breeder Cell",
                String.format("Every %d reactor hull heat", heatStep),
                String.format("increase speed by %d00%%", heatMultiplier),
                String.format("Required pulses: %d", requiredPulses));
        }

        public GT_Recipe addFakeRecipe(ItemStack input, ItemStack output, String... neiDesc) {
            GT_Recipe r = new GT_Recipe(
                new ItemStack[] { input },
                new ItemStack[] { output },
                null,
                new int[] { 10000 },
                null,
                null,
                0,
                0,
                0);
            r.setNeiDesc(neiDesc);
            return addRecipe(r, true, true, false);
        }
    }

    public static class GT_Recipe_Map_LargeChemicalReactor extends GT_Recipe_Map_LargeNEI {

        private static final int TOTAL_INPUT_COUNT = 6;
        private static final int OUTPUT_COUNT = 6;

        public GT_Recipe_Map_LargeChemicalReactor() {
            super(
                new HashSet<>(1000),
                "gt.recipe.largechemicalreactor",
                "Large Chemical Reactor",
                null,
                GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "LCRNEI"),
                TOTAL_INPUT_COUNT,
                OUTPUT_COUNT,
                0,
                0,
                1,
                E,
                1,
                E,
                true,
                true);
        }

        @Override
        public GT_Recipe addRecipe(boolean aOptimize, ItemStack[] aInputs, ItemStack[] aOutputs, Object aSpecial,
            int[] aOutputChances, FluidStack[] aFluidInputs, FluidStack[] aFluidOutputs, int aDuration, int aEUt,
            int aSpecialValue) {
            aOptimize = false;
            ArrayList<ItemStack> adjustedInputs = new ArrayList<>();
            ArrayList<ItemStack> adjustedOutputs = new ArrayList<>();
            ArrayList<FluidStack> adjustedFluidInputs = new ArrayList<>();
            ArrayList<FluidStack> adjustedFluidOutputs = new ArrayList<>();

            if (aInputs == null) {
                aInputs = new ItemStack[0];
            } else {
                aInputs = ArrayExt.withoutTrailingNulls(aInputs, ItemStack[]::new);
            }

            for (ItemStack input : aInputs) {
                FluidStack inputFluidContent = FluidContainerRegistry.getFluidForFilledItem(input);
                if (inputFluidContent != null) {
                    inputFluidContent.amount *= input.stackSize;
                    if (inputFluidContent.getFluid()
                        .getName()
                        .equals("ic2steam")) {
                        inputFluidContent = GT_ModHandler.getSteam(inputFluidContent.amount);
                    }
                    adjustedFluidInputs.add(inputFluidContent);
                } else {
                    ItemData itemData = GT_OreDictUnificator.getItemData(input);
                    if (itemData != null && itemData.hasValidPrefixMaterialData()
                        && itemData.mMaterial.mMaterial == Materials.Empty) {
                        continue;
                    } else {
                        if (itemData != null && itemData.hasValidPrefixMaterialData()
                            && itemData.mPrefix == OrePrefixes.cell) {
                            ItemStack dustStack = itemData.mMaterial.mMaterial.getDust(input.stackSize);
                            if (dustStack != null) {
                                adjustedInputs.add(dustStack);
                            } else {
                                adjustedInputs.add(input);
                            }
                        } else {
                            adjustedInputs.add(input);
                        }
                    }
                }

                if (aFluidInputs == null) {
                    aFluidInputs = new FluidStack[0];
                }
            }
            Collections.addAll(adjustedFluidInputs, aFluidInputs);
            aInputs = adjustedInputs.toArray(new ItemStack[0]);
            aFluidInputs = adjustedFluidInputs.toArray(new FluidStack[0]);

            if (aOutputs == null) {
                aOutputs = new ItemStack[0];
            } else {
                aOutputs = ArrayExt.withoutTrailingNulls(aOutputs, ItemStack[]::new);
            }

            for (ItemStack output : aOutputs) {
                FluidStack outputFluidContent = FluidContainerRegistry.getFluidForFilledItem(output);
                if (outputFluidContent != null) {
                    outputFluidContent.amount *= output.stackSize;
                    if (outputFluidContent.getFluid()
                        .getName()
                        .equals("ic2steam")) {
                        outputFluidContent = GT_ModHandler.getSteam(outputFluidContent.amount);
                    }
                    adjustedFluidOutputs.add(outputFluidContent);
                } else {
                    ItemData itemData = GT_OreDictUnificator.getItemData(output);
                    if (!(itemData != null && itemData.hasValidPrefixMaterialData()
                        && itemData.mMaterial.mMaterial == Materials.Empty)) {
                        adjustedOutputs.add(output);
                    }
                }
            }
            if (aFluidOutputs == null) {
                aFluidOutputs = new FluidStack[0];
            }
            Collections.addAll(adjustedFluidOutputs, aFluidOutputs);
            aOutputs = adjustedOutputs.toArray(new ItemStack[0]);
            aFluidOutputs = adjustedFluidOutputs.toArray(new FluidStack[0]);

            return super.addRecipe(
                aOptimize,
                aInputs,
                aOutputs,
                aSpecial,
                aOutputChances,
                aFluidInputs,
                aFluidOutputs,
                aDuration,
                aEUt,
                aSpecialValue);
        }
    }

    public static class GT_Recipe_Map_DistillationTower extends GT_Recipe_Map {

        public GT_Recipe_Map_DistillationTower() {
            super(
                new HashSet<>(110),
                "gt.recipe.distillationtower",
                "Distillation Tower",
                null,
                GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "DistillationTower"),
                2,
                1,
                0,
                0,
                1,
                E,
                1,
                E,
                true,
                true);
            setLogoPos(80, 62);
        }

        @Override
        public IDrawable getOverlayForSlot(boolean isFluid, boolean isOutput, int index, boolean isSpecial) {
            if (isOutput) {
                if (isFluid) {
                    return GT_UITextures.OVERLAY_SLOTS_NUMBER[index + 1];
                } else {
                    return GT_UITextures.OVERLAY_SLOTS_NUMBER[0];
                }
            }
            return super.getOverlayForSlot(isFluid, false, index, isSpecial);
        }

        @Override
        public List<Pos2d> getItemOutputPositions(int itemOutputCount) {
            return Collections.singletonList(new Pos2d(106, 62));
        }

        @Override
        public List<Pos2d> getFluidOutputPositions(int fluidOutputCount) {
            List<Pos2d> results = new ArrayList<>();
            for (int i = 1; i < fluidOutputCount + 1; i++) {
                results.add(new Pos2d(106 + (i % 3) * 18, 62 - (i / 3) * 18));
            }
            return results;
        }
    }

    public static class GT_Recipe_Map_OilCracker extends GT_Recipe_Map {

        private final Set<String> mValidCatalystFluidNames = new HashSet<>();

        public GT_Recipe_Map_OilCracker() {
            super(
                new HashSet<>(70),
                "gt.recipe.craker",
                "Oil Cracker",
                null,
                GregTech.getResourcePath(TEXTURES_GUI_BASICMACHINES, "OilCracker"),
                1,
                1,
                1,
                2,
                1,
                E,
                1,
                E,
                true,
                true);
        }

        @Override
        public GT_Recipe add(GT_Recipe aRecipe) {
            GT_Recipe ret = super.add(aRecipe);
            if (ret != null && ret.mFluidInputs != null && ret.mFluidInputs.length > 1 && ret.mFluidInputs[1] != null) {
                mValidCatalystFluidNames.add(
                    ret.mFluidInputs[1].getFluid()
                        .getName());
            }
            return ret;
        }

        public boolean isValidCatalystFluid(FluidStack aFluidStack) {
            return mValidCatalystFluidNames.contains(
                aFluidStack.getFluid()
                    .getName());
        }
    }

    public static class GT_Recipe_WithAlt extends GT_Recipe {

        ItemStack[][] mOreDictAlt;

        GT_Recipe_WithAlt(ItemStack[] mInputs, ItemStack[] mOutputs, FluidStack[] mFluidInputs,
            FluidStack[] mFluidOutputs, int[] mChances, Object mSpecialItems, int mDuration, int mEUt,
            int mSpecialValue, boolean mEnabled, boolean mHidden, boolean mFakeRecipe, boolean mCanBeBuffered,
            boolean mNeedsEmptyOutput, String[] neiDesc, ItemStack[][] mOreDictAlt) {
            super(
                mInputs,
                mOutputs,
                mFluidInputs,
                mFluidOutputs,
                mChances,
                mSpecialItems,
                mDuration,
                mEUt,
                mSpecialValue,
                mEnabled,
                mHidden,
                mFakeRecipe,
                mCanBeBuffered,
                mNeedsEmptyOutput,
                neiDesc);
            this.mOreDictAlt = mOreDictAlt;
        }

        public GT_Recipe_WithAlt(boolean aOptimize, ItemStack[] aInputs, ItemStack[] aOutputs, Object aSpecialItems,
            int[] aChances, FluidStack[] aFluidInputs, FluidStack[] aFluidOutputs, int aDuration, int aEUt,
            int aSpecialValue, ItemStack[][] aAlt) {
            super(
                aOptimize,
                aInputs,
                aOutputs,
                aSpecialItems,
                aChances,
                aFluidInputs,
                aFluidOutputs,
                aDuration,
                aEUt,
                aSpecialValue);
            mOreDictAlt = aAlt;
        }

        public Object getAltRepresentativeInput(int aIndex) {
            if (aIndex < 0) return null;
            if (aIndex < mOreDictAlt.length) {
                if (mOreDictAlt[aIndex] != null && mOreDictAlt[aIndex].length > 0) {
                    ItemStack[] rStacks = new ItemStack[mOreDictAlt[aIndex].length];
                    for (int i = 0; i < mOreDictAlt[aIndex].length; i++) {
                        rStacks[i] = GT_Utility.copyOrNull(mOreDictAlt[aIndex][i]);
                    }
                    return rStacks;
                }
            }
            if (aIndex >= mInputs.length) return null;
            return GT_Utility.copyOrNull(mInputs[aIndex]);
        }
    }

    private static class ReplicatorFakeMap extends GT_Recipe_Map {

        public ReplicatorFakeMap(Collection<GT_Recipe> aRecipeList, String aUnlocalizedName, String aLocalName,
            String aNEIName, String aNEIGUIPath, int aUsualInputCount, int aUsualOutputCount, int aMinimalInputItems,
            int aMinimalInputFluids, int aAmperage, String aNEISpecialValuePre, int aNEISpecialValueMultiplier,
            String aNEISpecialValuePost, boolean aShowVoltageAmperageInNEI, boolean aNEIAllowed) {
            super(
                aRecipeList,
                aUnlocalizedName,
                aLocalName,
                aNEIName,
                aNEIGUIPath,
                aUsualInputCount,
                aUsualOutputCount,
                aMinimalInputItems,
                aMinimalInputFluids,
                aAmperage,
                aNEISpecialValuePre,
                aNEISpecialValueMultiplier,
                aNEISpecialValuePost,
                aShowVoltageAmperageInNEI,
                aNEIAllowed);
        }

        @Override
        public GT_Recipe addFakeRecipe(boolean aCheckForCollisions, ItemStack[] aInputs, ItemStack[] aOutputs,
            Object aSpecial, FluidStack[] aFluidInputs, FluidStack[] aFluidOutputs, int aDuration, int aEUt,
            int aSpecialValue) {
            AtomicInteger ai = new AtomicInteger();
            Optional.ofNullable(GT_OreDictUnificator.getAssociation(aOutputs[0]))
                .map(itemData -> itemData.mMaterial)
                .map(materialsStack -> materialsStack.mMaterial)
                .map(materials -> materials.mElement)
                .map(Element::getMass)
                .ifPresent(e -> {
                    aFluidInputs[0].amount = (int) GT_MetaTileEntity_Replicator.cubicFluidMultiplier(e);
                    ai.set(GT_Utility.safeInt(aFluidInputs[0].amount * 512L, 1));
                });
            return addFakeRecipe(
                aCheckForCollisions,
                new GT_Recipe(
                    false,
                    aInputs,
                    aOutputs,
                    aSpecial,
                    null,
                    aFluidInputs,
                    aFluidOutputs,
                    ai.get(),
                    aEUt,
                    aSpecialValue));
        }
    }

    public static class GT_Recipe_Map_ComplexFusion extends GT_Recipe_Map {

        public GT_Recipe_Map_ComplexFusion(Collection<GT_Recipe> aRecipeList, String aUnlocalizedName,
            String aLocalName, String aNEIName, String aNEIGUIPath, int aUsualInputCount, int aUsualOutputCount,
            int aMinimalInputItems, int aMinimalInputFluids, int aAmperage, String aNEISpecialValuePre,
            int aNEISpecialValueMultiplier, String aNEISpecialValuePost, boolean aShowVoltageAmperageInNEI,
            boolean aNEIAllowed) {
            super(
                aRecipeList,
                aUnlocalizedName,
                aLocalName,
                aNEIName,
                aNEIGUIPath,
                aUsualInputCount,
                aUsualOutputCount,
                aMinimalInputItems,
                aMinimalInputFluids,
                aAmperage,
                aNEISpecialValuePre,
                aNEISpecialValueMultiplier,
                aNEISpecialValuePost,
                aShowVoltageAmperageInNEI,
                aNEIAllowed);
        }

        @Override
        public GT_Recipe addRecipe(int[] aOutputChances, FluidStack[] aFluidInputs, FluidStack[] aFluidOutputs,
            int aDuration, int aEUt, int aSpecialValue) {
            return addRecipe(
                new GT_Recipe(
                    false,
                    null,
                    null,
                    null,
                    aOutputChances,
                    aFluidInputs,
                    aFluidOutputs,
                    aDuration,
                    aEUt,
                    aSpecialValue),
                false,
                false,
                false);
        }

        @Override
        public List<Pos2d> getFluidInputPositions(int fluidInputCount) {
            return UIHelper.getGridPositions(fluidInputCount, 7, 9, 4);
        }

        @Override
        public List<Pos2d> getFluidOutputPositions(int fluidOutputCount) {
            return UIHelper.getGridPositions(fluidOutputCount, 97, 9, 4);
        }
    }

    public static class GT_Recipe_Map_AssemblyLineFake extends GT_Recipe_Map {

        public GT_Recipe_Map_AssemblyLineFake(Collection<GT_Recipe> aRecipeList, String aUnlocalizedName,
            String aLocalName, String aNEIName, String aNEIGUIPath, int aUsualInputCount, int aUsualOutputCount,
            int aMinimalInputItems, int aMinimalInputFluids, int aAmperage, String aNEISpecialValuePre,
            int aNEISpecialValueMultiplier, String aNEISpecialValuePost, boolean aShowVoltageAmperageInNEI,
            boolean aNEIAllowed) {
            super(
                aRecipeList,
                aUnlocalizedName,
                aLocalName,
                aNEIName,
                aNEIGUIPath,
                aUsualInputCount,
                aUsualOutputCount,
                aMinimalInputItems,
                aMinimalInputFluids,
                aAmperage,
                aNEISpecialValuePre,
                aNEISpecialValueMultiplier,
                aNEISpecialValuePost,
                aShowVoltageAmperageInNEI,
                aNEIAllowed);
            setNEITransferRect(new Rectangle(146, 26, 10, 18));
        }

        @Override
        public List<Pos2d> getItemInputPositions(int itemInputCount) {
            return UIHelper.getGridPositions(itemInputCount, 16, 8, 4);
        }

        @Override
        public List<Pos2d> getItemOutputPositions(int itemOutputCount) {
            return Collections.singletonList(new Pos2d(142, 8));
        }

        @Override
        public Pos2d getSpecialItemPosition() {
            return new Pos2d(142, 44);
        }

        @Override
        public List<Pos2d> getFluidInputPositions(int fluidInputCount) {
            return UIHelper.getGridPositions(fluidInputCount, 106, 8, 1);
        }

        @Override
        public void addProgressBarUI(ModularWindow.Builder builder, Supplier<Float> progressSupplier,
            Pos2d windowOffset) {
            int bar1Width = 17;
            int bar2Width = 18;
            builder.widget(
                new ProgressBar().setTexture(GT_UITextures.PROGRESSBAR_ASSEMBLY_LINE_1, 17)
                    .setDirection(ProgressBar.Direction.RIGHT)
                    .setProgress(() -> progressSupplier.get() * ((float) (bar1Width + bar2Width) / bar1Width))
                    .setSynced(false, false)
                    .setPos(new Pos2d(88, 8).add(windowOffset))
                    .setSize(bar1Width, 72));
            builder.widget(
                new ProgressBar().setTexture(GT_UITextures.PROGRESSBAR_ASSEMBLY_LINE_2, 18)
                    .setDirection(ProgressBar.Direction.RIGHT)
                    .setProgress(
                        () -> (progressSupplier.get() - ((float) bar1Width / (bar1Width + bar2Width)))
                            * ((float) (bar1Width + bar2Width) / bar2Width))
                    .setSynced(false, false)
                    .setPos(new Pos2d(124, 8).add(windowOffset))
                    .setSize(bar2Width, 72));
            builder.widget(
                new ProgressBar().setTexture(GT_UITextures.PROGRESSBAR_ASSEMBLY_LINE_3, 18)
                    .setDirection(ProgressBar.Direction.UP)
                    .setProgress(progressSupplier)
                    .setSynced(false, false)
                    .setPos(new Pos2d(146, 26).add(windowOffset))
                    .setSize(10, 18));
        }
    }
}