/* * Copyright (c) 2018-2020 bartimaeusnek Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in the Software without restriction, * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following * conditions: The above copyright notice and this permission notice shall be included in all copies or substantial * portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ package bartworks.common.loaders; import static gregtech.api.enums.Mods.TinkerConstruct; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import net.minecraft.item.ItemStack; import net.minecraftforge.fluids.FluidContainerRegistry; import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.oredict.OreDictionary; import org.apache.commons.lang3.StringUtils; import com.google.common.collect.ArrayListMultimap; import bartworks.API.recipe.DynamicGTRecipe; import bartworks.MainMod; import bartworks.system.material.Werkstoff; import bartworks.system.material.WerkstoffLoader; import bartworks.util.BWUtil; import bartworks.util.log.DebugLog; import bwcrossmod.BartWorksCrossmod; import cpw.mods.fml.common.registry.GameRegistry; import gnu.trove.map.hash.TObjectDoubleHashMap; import gregtech.api.enums.Element; import gregtech.api.enums.Materials; import gregtech.api.enums.OrePrefixes; import gregtech.api.enums.SubTag; import gregtech.api.objects.GTItemStack; import gregtech.api.recipe.RecipeMap; import gregtech.api.recipe.RecipeMaps; import gregtech.api.util.GTModHandler; import gregtech.api.util.GTOreDictUnificator; import gregtech.api.util.GTRecipe; import gregtech.api.util.GTUtility; public class StaticRecipeChangeLoaders { private static TObjectDoubleHashMap gtEbfGasRecipeTimeMultipliers = null; private static TObjectDoubleHashMap gtEbfGasRecipeConsumptionMultipliers = null; public static final List whitelistForEBFNoGasRecipeDontCheckItemData = Collections .singletonList(GTModHandler.getModItem(TinkerConstruct.ID, "materials", 1L, 12) // Raw Aluminum -> Aluminium // Ingot // (coremod) ); private StaticRecipeChangeLoaders() {} public static void addEBFGasRecipes() { if (gtEbfGasRecipeTimeMultipliers == null) { // For Werkstoff gases, use Werkstoff.Stats.setEbfGasRecipeTimeMultiplier gtEbfGasRecipeTimeMultipliers = new TObjectDoubleHashMap<>(10, 0.5F, -1.0D); // keep default value as -1 // Example to make Argon cut recipe times into a third of the original: // gtEbfGasRecipeTimeMultipliers.put(Materials.Argon, 1.0D / 3.0D); gtEbfGasRecipeTimeMultipliers.put(Materials.Nitrogen, 1.0D); gtEbfGasRecipeTimeMultipliers.put(Materials.Helium, 0.9D); gtEbfGasRecipeTimeMultipliers.put(Materials.Argon, 0.8D); gtEbfGasRecipeTimeMultipliers.put(Materials.Radon, 0.7D); } if (gtEbfGasRecipeConsumptionMultipliers == null) { // For Werkstoff gases, use Werkstoff.Stats.setEbfGasRecipeConsumedAmountMultiplier gtEbfGasRecipeConsumptionMultipliers = new TObjectDoubleHashMap<>(10, 0.5F, 1.0D); // keep default value as // 1 // Example to make Argon recipes use half the gas amount of the primary recipe (1000L->500L, 2000L->1000L // etc.): // gtEbfGasRecipeConsumptionMultipliers.put(Materials.Argon, 1.0D / 2.0D); gtEbfGasRecipeConsumptionMultipliers.put(Materials.Nitrogen, 1.0D); gtEbfGasRecipeConsumptionMultipliers.put(Materials.Helium, 1.0D); gtEbfGasRecipeConsumptionMultipliers.put(Materials.Argon, 0.85D); gtEbfGasRecipeConsumptionMultipliers.put(Materials.Radon, 0.7D); } ArrayListMultimap toChange = getRecipesToChange( WerkstoffLoader.NOBLE_GAS, WerkstoffLoader.ANAEROBE_GAS); editRecipes(toChange, getNoGasItems(toChange)); } public static void unificationRecipeEnforcer() { List toRemove = new ArrayList<>(); final OrePrefixes[] OREPREFIX_VALUES = OrePrefixes.values(); for (Werkstoff werkstoff : Werkstoff.werkstoffHashSet) { StaticRecipeChangeLoaders.runMaterialLinker(werkstoff); if (werkstoff.getGenerationFeatures().enforceUnification) { HashSet oreDictNames = new HashSet<>(werkstoff.getADDITIONAL_OREDICT()); oreDictNames.add(werkstoff.getVarName()); StaticRecipeChangeLoaders.runMoltenUnificationEnfocement(werkstoff); StaticRecipeChangeLoaders.runUnficationDeleter(werkstoff); for (String s : oreDictNames) { for (OrePrefixes prefixes : OREPREFIX_VALUES) { if (!werkstoff.hasItemType(prefixes)) continue; String fullOreName = prefixes + s; List ores = OreDictionary.getOres(fullOreName, false); if (ores.size() <= 1) // empty or one entry, i.e. no unification needed continue; for (ItemStack toReplace : ores) { ItemStack replacement = werkstoff.get(prefixes); if (toReplace == null || GTUtility.areStacksEqual(toReplace, replacement) || replacement == null || replacement.getItem() == null) continue; for (RecipeMap map : RecipeMap.ALL_RECIPE_MAPS.values()) { toRemove.clear(); nextRecipe: for (GTRecipe recipe : map.getAllRecipes()) { boolean removal = map.equals(RecipeMaps.fluidExtractionRecipes) || map.equals(RecipeMaps.fluidSolidifierRecipes); for (int i = 0; i < recipe.mInputs.length; i++) { if (!GTUtility.areStacksEqual(recipe.mInputs[i], toReplace)) continue; if (removal) { toRemove.add(recipe); continue nextRecipe; } recipe.mInputs[i] = GTUtility .copyAmount(recipe.mInputs[i].stackSize, replacement); } for (int i = 0; i < recipe.mOutputs.length; i++) { if (!GTUtility.areStacksEqual(recipe.mOutputs[i], toReplace)) continue; if (removal) { toRemove.add(recipe); continue nextRecipe; } recipe.mOutputs[i] = GTUtility .copyAmount(recipe.mOutputs[i].stackSize, replacement); } if (recipe.mSpecialItems instanceof ItemStack specialItemStack) { if (!GTUtility.areStacksEqual(specialItemStack, toReplace)) continue; if (removal) { toRemove.add(recipe); continue nextRecipe; } recipe.mSpecialItems = GTUtility .copyAmount(specialItemStack.stackSize, replacement); } } map.getBackend() .removeRecipes(toRemove); } } } } } } } private static void runMoltenUnificationEnfocement(Werkstoff werkstoff) { if (werkstoff.getGenerationFeatures().enforceUnification && werkstoff.hasItemType(OrePrefixes.cellMolten)) { try { FluidContainerRegistry.FluidContainerData data = new FluidContainerRegistry.FluidContainerData( new FluidStack(Objects.requireNonNull(WerkstoffLoader.molten.get(werkstoff)), 144), werkstoff.get(OrePrefixes.cellMolten), Materials.Empty.getCells(1)); Field f = GTUtility.class.getDeclaredField("sFilledContainerToData"); f.setAccessible(true); @SuppressWarnings("unchecked") Map sFilledContainerToData = (Map) f .get(null); Set> toremFilledContainerToData = new HashSet<>(); ItemStack toReplace = null; for (Map.Entry entry : sFilledContainerToData .entrySet()) { final String MODID = GameRegistry.findUniqueIdentifierFor(data.filledContainer.getItem()).modId; if (MainMod.MOD_ID.equals(MODID) || BartWorksCrossmod.MOD_ID.equals(MODID)) continue; if (entry.getValue().fluid.equals(data.fluid) && !entry.getValue().filledContainer.equals(data.filledContainer)) { toReplace = entry.getValue().filledContainer; toremFilledContainerToData.add(entry); } } sFilledContainerToData.entrySet() .removeAll(toremFilledContainerToData); Set toremRecipeList = new HashSet<>(); if (toReplace != null) { for (RecipeMap map : RecipeMap.ALL_RECIPE_MAPS.values()) { toremRecipeList.clear(); for (GTRecipe recipe : map.getAllRecipes()) { for (ItemStack mInput : recipe.mInputs) { if (GTUtility.areStacksEqual(mInput, toReplace)) { toremRecipeList.add(recipe); // recipe.mInputs[i] = data.filledContainer; } } for (ItemStack mOutput : recipe.mOutputs) { if (GTUtility.areStacksEqual(mOutput, toReplace)) { toremRecipeList.add(recipe); // recipe.mOutputs[i] = data.filledContainer; if (map == RecipeMaps.fluidCannerRecipes && GTUtility.areStacksEqual(mOutput, data.filledContainer) && !recipe.mFluidInputs[0].equals(data.fluid)) { toremRecipeList.add(recipe); // recipe.mOutputs[i] = data.filledContainer; } } } if (recipe.mSpecialItems instanceof ItemStack && GTUtility.areStacksEqual((ItemStack) recipe.mSpecialItems, toReplace)) { toremRecipeList.add(recipe); // recipe.mSpecialItems = data.filledContainer; } } map.getBackend() .removeRecipes(toremRecipeList); } } GTUtility.addFluidContainerData(data); } catch (NoSuchFieldException | IllegalAccessException | ClassCastException e) { e.printStackTrace(); } } } private static void runUnficationDeleter(Werkstoff werkstoff) { if (werkstoff.getType() == Werkstoff.Types.ELEMENT && werkstoff.getBridgeMaterial() != null && Element.get(werkstoff.getToolTip()) != Element._NULL) { werkstoff.getBridgeMaterial().mElement = Element.get(werkstoff.getToolTip()); Element.get(werkstoff.getToolTip()).mLinkedMaterials = new ArrayList<>(); Element.get(werkstoff.getToolTip()).mLinkedMaterials.add(werkstoff.getBridgeMaterial()); } for (OrePrefixes prefixes : OrePrefixes.values()) if (werkstoff.hasItemType(prefixes)) { GTOreDictUnificator.set(prefixes, werkstoff.getBridgeMaterial(), werkstoff.get(prefixes), true, true); for (ItemStack stack : OreDictionary.getOres(prefixes + werkstoff.getVarName())) { GTOreDictUnificator.addAssociation(prefixes, werkstoff.getBridgeMaterial(), stack, false); GTOreDictUnificator.getAssociation(stack).mUnificationTarget = werkstoff.get(prefixes); } } } private static void runMaterialLinker(Werkstoff werkstoff) { if (werkstoff.getType() == Werkstoff.Types.ELEMENT && werkstoff.getBridgeMaterial() != null && Element.get(werkstoff.getToolTip()) != Element._NULL) { werkstoff.getBridgeMaterial().mElement = Element.get(werkstoff.getToolTip()); Element.get(werkstoff.getToolTip()).mLinkedMaterials = new ArrayList<>(); Element.get(werkstoff.getToolTip()).mLinkedMaterials.add(werkstoff.getBridgeMaterial()); } for (OrePrefixes prefixes : OrePrefixes.values()) if (werkstoff.hasItemType(prefixes) && werkstoff.getBridgeMaterial() != null) { GTOreDictUnificator.set(prefixes, werkstoff.getBridgeMaterial(), werkstoff.get(prefixes), true, true); for (ItemStack stack : OreDictionary.getOres(prefixes + werkstoff.getVarName())) { GTOreDictUnificator.addAssociation(prefixes, werkstoff.getBridgeMaterial(), stack, false); } } } /** * Constructs a list of recipes to change by scanning all EBF recipes for uses of noble gases. * * @param GasTags list of gas tags to look out for in EBF recipes * @return A multimap from the gas tag (noble and/or anaerobic) to all the recipes containing a gas with that tag */ private static ArrayListMultimap getRecipesToChange(SubTag... GasTags) { ArrayListMultimap toAdd = ArrayListMultimap.create(); for (GTRecipe recipe : RecipeMaps.blastFurnaceRecipes.getAllRecipes()) { if (recipe.mFluidInputs != null && recipe.mFluidInputs.length > 0) { Materials mat = getMaterialFromInputFluid(recipe); if (mat != Materials._NULL) { for (SubTag tag : GasTags) { if (mat.contains(tag)) { DebugLog.log( "Found EBF Recipe to change, Output:" + BWUtil.translateGTItemStack(recipe.mOutputs[0])); toAdd.put(tag, recipe); } } } } } return toAdd; } /** * Scans EBF recipes for no-gas variants of the recipes present in base. Adds these recipes to the base multimap. * * @param base The recipe multimap to scan and modify * @return Set of item outputs (recipe.mOutputs[0]) of the no-gas recipes */ private static HashSet getNoGasItems(ArrayListMultimap base) { HashSet toAdd = new HashSet<>(); ArrayListMultimap repToAdd = ArrayListMultimap.create(); for (GTRecipe recipe : RecipeMaps.blastFurnaceRecipes.getAllRecipes()) { for (SubTag tag : base.keySet()) recipeLoop: for (GTRecipe baseRe : base.get(tag)) { if (recipe.mInputs.length == baseRe.mInputs.length && recipe.mOutputs.length == baseRe.mOutputs.length) for (int i = 0; i < recipe.mInputs.length; i++) { ItemStack tmpInput = recipe.mInputs[i]; if ((recipe.mFluidInputs == null || recipe.mFluidInputs.length == 0) && (whitelistForEBFNoGasRecipeDontCheckItemData.stream() .anyMatch(s -> GTUtility.areStacksEqual(s, tmpInput)) || BWUtil.checkStackAndPrefix(recipe.mInputs[i]) && BWUtil.checkStackAndPrefix(baseRe.mInputs[i]) && GTOreDictUnificator.getAssociation(recipe.mInputs[i]).mMaterial.mMaterial.equals( GTOreDictUnificator.getAssociation(baseRe.mInputs[i]).mMaterial.mMaterial) && GTUtility.areStacksEqual(recipe.mOutputs[0], baseRe.mOutputs[0]))) { toAdd.add(recipe.mOutputs[0]); repToAdd.put(tag, recipe); continue recipeLoop; } } } } base.putAll(repToAdd); return toAdd; } private static int transformEBFGasRecipeTime(int originalDuration, long originalGasProtons, long newGasProtons) { double protonTerm = originalGasProtons * (newGasProtons >= originalGasProtons ? 1.0D : 2.75D) - newGasProtons; return Math.max(1, (int) (originalDuration / 200D * Math.max(200D + protonTerm, 1D))); } private static int transformEBFGasRecipeTime(GTRecipe recipe, Materials originalGas, Materials newGas) { double newEbfMul = gtEbfGasRecipeTimeMultipliers.get(newGas); double originalEbfMul = gtEbfGasRecipeTimeMultipliers.get(originalGas); if (newEbfMul < 0.0D || originalEbfMul < 0.0D) { return transformEBFGasRecipeTime(recipe.mDuration, originalGas.getProtons(), newGas.getProtons()); } return Math.max(1, (int) (recipe.mDuration * newEbfMul / originalEbfMul)); } private static int transformEBFGasRecipeTime(GTRecipe recipe, Materials originalGas, Werkstoff newGas) { double newEbfMul = newGas.getStats() .getEbfGasRecipeTimeMultiplier(); double originalEbfMul = gtEbfGasRecipeTimeMultipliers.get(originalGas); if (newEbfMul < 0.0D || originalEbfMul < 0.0D) { return transformEBFGasRecipeTime( recipe.mDuration, originalGas.getProtons(), newGas.getStats() .getProtons()); } return Math.max(1, (int) (recipe.mDuration * newEbfMul / originalEbfMul)); } private static int transformEBFNoGasRecipeTime(GTRecipe recipe, Materials originalGas) { return transformEBFGasRecipeTime(recipe.mDuration, originalGas.getProtons(), 0); } private static void editEBFMaterialRecipes(SubTag GasTag, GTRecipe recipe, Materials originalGas, HashSet toAdd) { for (Materials newGas : Materials.values()) { if (newGas.contains(GasTag)) { int time = transformEBFGasRecipeTime(recipe, originalGas, newGas); int gasAmount = Math.max( 1, (int) Math.round(recipe.mFluidInputs[0].amount * gtEbfGasRecipeConsumptionMultipliers.get(newGas))); if (recipe.mFluidInputs != null && recipe.mFluidInputs.length == 1 && recipe.mFluidInputs[0].isFluidEqual(newGas.getGas(0))) { // preserve original recipe owner toAdd.add( new DynamicGTRecipe( false, recipe.mInputs, recipe.mOutputs, recipe.mSpecialItems, recipe.mChances, new FluidStack[] { newGas.getGas(gasAmount) }, recipe.mFluidOutputs, time, recipe.mEUt, recipe.mSpecialValue, recipe)); } else { // new recipe toAdd.add( new GTRecipe( false, recipe.mInputs, recipe.mOutputs, recipe.mSpecialItems, recipe.mChances, new FluidStack[] { newGas.getGas(gasAmount) }, recipe.mFluidOutputs, time, recipe.mEUt, recipe.mSpecialValue)); } } } } private static void editEBFWerkstoffRecipes(SubTag GasTag, GTRecipe recipe, Materials originalGas, HashSet toAdd) { for (Werkstoff newGas : Werkstoff.werkstoffHashMap.values()) { if (newGas.contains(GasTag)) { int time = transformEBFGasRecipeTime(recipe, originalGas, newGas); int gasAmount = Math.max( 1, (int) Math.round( recipe.mFluidInputs[0].amount * newGas.getStats() .getEbfGasRecipeConsumedAmountMultiplier())); if (recipe.mFluidInputs != null && recipe.mFluidInputs.length == 1 && recipe.mFluidInputs[0] .isFluidEqual(new FluidStack(Objects.requireNonNull(WerkstoffLoader.fluids.get(newGas)), 0))) { // preserve original recipe owner toAdd.add( new DynamicGTRecipe( false, recipe.mInputs, recipe.mOutputs, recipe.mSpecialItems, recipe.mChances, new FluidStack[] { new FluidStack(Objects.requireNonNull(WerkstoffLoader.fluids.get(newGas)), gasAmount) }, recipe.mFluidOutputs, time, recipe.mEUt, recipe.mSpecialValue, recipe)); } else { // new recipe toAdd.add( new GTRecipe( false, recipe.mInputs, recipe.mOutputs, recipe.mSpecialItems, recipe.mChances, new FluidStack[] { new FluidStack(Objects.requireNonNull(WerkstoffLoader.fluids.get(newGas)), gasAmount) }, recipe.mFluidOutputs, time, recipe.mEUt, recipe.mSpecialValue)); } } } } private static void editEBFNoGasRecipes(GTRecipe recipe, Materials originalGas, HashSet toAdd, HashSet noGas) { for (ItemStack is : noGas) { byte circuitConfiguration = 1; if (GTUtility.areStacksEqual(is, recipe.mOutputs[0])) { ArrayList inputs = new ArrayList<>(recipe.mInputs.length); for (ItemStack stack : recipe.mInputs) if (!GTUtility.areStacksEqual(GTUtility.getIntegratedCircuit(11), stack) && !GTUtility.areStacksEqual(GTUtility.getIntegratedCircuit(14), stack) && !GTUtility.areStacksEqual(GTUtility.getIntegratedCircuit(19), stack)) { if (BWUtil.checkStackAndPrefix(stack)) circuitConfiguration = (byte) (OrePrefixes.dustSmall .equals(GTOreDictUnificator.getAssociation(stack).mPrefix) ? 4 : OrePrefixes.dustTiny.equals(GTOreDictUnificator.getAssociation(stack).mPrefix) ? 9 : 1); inputs.add(stack); } inputs.add(GTUtility.getIntegratedCircuit(circuitConfiguration)); toAdd.add( new DynamicGTRecipe( false, inputs.toArray(new ItemStack[0]), recipe.mOutputs, recipe.mSpecialItems, recipe.mChances, null, recipe.mFluidOutputs, transformEBFNoGasRecipeTime(recipe, originalGas), recipe.mEUt, recipe.mSpecialValue, recipe)); break; } } } private static void removeDuplicateGasRecipes(HashSet toAdd) { HashSet duplicates = new HashSet<>(); for (GTRecipe recipe : toAdd) { for (GTRecipe recipe2 : toAdd) { if (recipe.mEUt != recipe2.mEUt || recipe.mDuration != recipe2.mDuration || recipe.mSpecialValue != recipe2.mSpecialValue || recipe == recipe2 || recipe.mInputs.length != recipe2.mInputs.length || recipe.mFluidInputs.length != recipe2.mFluidInputs.length) continue; boolean isSame = true; for (int i = 0; i < recipe.mInputs.length; i++) { if (!GTUtility.areStacksEqual(recipe.mInputs[i], recipe2.mInputs[i])) isSame = false; } for (int i = 0; i < recipe.mFluidInputs.length; i++) { if (!GTUtility.areFluidsEqual(recipe.mFluidInputs[i], recipe2.mFluidInputs[i])) isSame = false; } if (isSame) duplicates.add(recipe2); } } toAdd.removeAll(duplicates); } private static Materials getMaterialFromInputFluid(GTRecipe recipe) { String materialString = recipe.mFluidInputs[0].getFluid() .getName(); materialString = StringUtils.removeStart(materialString, "molten"); materialString = StringUtils.removeStart(materialString, "fluid"); materialString = StringUtils.capitalize(materialString); return Materials.get(materialString); } private static void editRecipes(ArrayListMultimap base, HashSet noGas) { HashSet toAdd = new HashSet<>(); for (SubTag gasTag : base.keySet()) { for (GTRecipe recipe : base.get(gasTag)) { if (recipe.mFluidInputs != null && recipe.mFluidInputs.length > 0) { Materials originalGas = getMaterialFromInputFluid(recipe); if (originalGas != Materials._NULL) { editEBFWerkstoffRecipes(gasTag, recipe, originalGas, toAdd); editEBFMaterialRecipes(gasTag, recipe, originalGas, toAdd); editEBFNoGasRecipes(recipe, originalGas, toAdd, noGas); } } } RecipeMaps.blastFurnaceRecipes.getBackend() .removeRecipes(base.get(gasTag)); } removeDuplicateGasRecipes(toAdd); toAdd.forEach(RecipeMaps.blastFurnaceRecipes::add); } public static void addElectricImplosionCompressorRecipes() { // Custom EIC recipes. new ElectricImplosionCompressorRecipes().run(); } }