diff --git a/src/main/java/gregtech/api/capability/impl/AbstractRecipeLogic.java b/src/main/java/gregtech/api/capability/impl/AbstractRecipeLogic.java index 455ae3afd47..8f69bc71407 100644 --- a/src/main/java/gregtech/api/capability/impl/AbstractRecipeLogic.java +++ b/src/main/java/gregtech/api/capability/impl/AbstractRecipeLogic.java @@ -14,6 +14,7 @@ import gregtech.api.metatileentity.multiblock.ParallelLogicType; import gregtech.api.recipes.Recipe; import gregtech.api.recipes.RecipeBuilder; +import gregtech.api.recipes.RecipeContext; import gregtech.api.recipes.RecipeMap; import gregtech.api.recipes.logic.IParallelableRecipeLogic; import gregtech.api.recipes.logic.OCParams; @@ -21,9 +22,11 @@ import gregtech.api.recipes.properties.RecipePropertyStorage; import gregtech.api.recipes.properties.impl.CleanroomProperty; import gregtech.api.recipes.properties.impl.DimensionProperty; +import gregtech.api.util.FluidStackHashStrategy; import gregtech.api.util.GTLog; import gregtech.api.util.GTTransferUtils; import gregtech.api.util.GTUtility; +import gregtech.api.util.ItemStackHashStrategy; import gregtech.common.ConfigHolder; import net.minecraft.item.ItemStack; @@ -72,6 +75,14 @@ public abstract class AbstractRecipeLogic extends MTETrait implements IWorkable, protected List fluidOutputs; protected List itemOutputs; + private final RecipeContext itemContext = new RecipeContext<>(ItemStackHashStrategy.builder() + .compareItem(true) + .compareDamage(true) + .build()); + private final RecipeContext fluidContext = new RecipeContext<>(FluidStackHashStrategy.builder() + .compareFluid(true) + .build()); + protected boolean isActive; protected boolean workingEnabled = true; protected boolean hasNotEnoughEnergy; @@ -390,6 +401,12 @@ protected void trySearchNewRecipe() { } // If a recipe was found, then inputs were valid. Cache found recipe. if (currentRecipe != null) { + + // we found a new recipe, clear the cache + if (this.previousRecipe != null && !currentRecipe.equals(this.previousRecipe)) { + this.itemContext.getCache().clear(); + this.fluidContext.getCache().clear(); + } this.previousRecipe = currentRecipe; } this.invalidInputsForRecipes = (currentRecipe == null); @@ -949,9 +966,11 @@ protected void setupRecipe(@NotNull Recipe recipe) { RecipeMap map = getRecipeMap(); if (map != null) { this.fluidOutputs = GTUtility - .copyFluidList(recipe.getResultFluidOutputs(recipeTier, machineTier, map)); + .copyFluidList(recipe.getResultFluidOutputs( + fluidContext.update(map.getChanceFunction(), recipeTier, machineTier))); this.itemOutputs = GTUtility - .copyStackList(recipe.getResultItemOutputs(recipeTier, machineTier, map)); + .copyStackList(recipe.getResultItemOutputs( + itemContext.update(map.getChanceFunction(), recipeTier, machineTier))); } if (this.wasActiveAndNeedsUpdate) { @@ -1211,6 +1230,21 @@ public NBTTagCompound serializeNBT() { } compound.setTag("ItemOutputs", itemOutputsList); compound.setTag("FluidOutputs", fluidOutputsList); + + NBTTagList itemCache = new NBTTagList(); + for (var entry : itemContext.getCache().entrySet()) { + var tag = entry.getKey().serializeNBT(); + tag.setInteger("CachedChance", entry.getValue()); + itemCache.appendTag(tag); + } + NBTTagList fluidCache = new NBTTagList(); + for (var entry : fluidContext.getCache().entrySet()) { + var tag = entry.getKey().writeToNBT(new NBTTagCompound()); + tag.setInteger("CachedChance", entry.getValue()); + fluidCache.appendTag(tag); + } + compound.setTag("ItemChanceCache", itemCache); + compound.setTag("FluidChanceCache", fluidCache); } return compound; } @@ -1237,6 +1271,20 @@ public void deserializeNBT(@NotNull NBTTagCompound compound) { for (int i = 0; i < fluidOutputsList.tagCount(); i++) { this.fluidOutputs.add(FluidStack.loadFluidStackFromNBT(fluidOutputsList.getCompoundTagAt(i))); } + + NBTTagList itemCache = compound.getTagList("ItemChanceCache", Constants.NBT.TAG_COMPOUND); + for (int i = 0; i < itemCache.tagCount(); i++) { + var stack = itemCache.getCompoundTagAt(i); + int cache = stack.getInteger("CachedChance"); + this.itemContext.updateCachedChance(new ItemStack(stack), cache); + } + + NBTTagList fluidCache = compound.getTagList("FluidChanceCache", Constants.NBT.TAG_COMPOUND); + for (int i = 0; i < fluidCache.tagCount(); i++) { + var stack = fluidCache.getCompoundTagAt(i); + int cache = stack.getInteger("CachedChance"); + this.fluidContext.updateCachedChance(FluidStack.loadFluidStackFromNBT(stack), cache); + } } } } diff --git a/src/main/java/gregtech/api/capability/impl/miner/MinerLogic.java b/src/main/java/gregtech/api/capability/impl/miner/MinerLogic.java index 2565c02a7a5..8a916541fad 100644 --- a/src/main/java/gregtech/api/capability/impl/miner/MinerLogic.java +++ b/src/main/java/gregtech/api/capability/impl/miner/MinerLogic.java @@ -4,6 +4,7 @@ import gregtech.api.capability.IMiner; import gregtech.api.metatileentity.MetaTileEntity; import gregtech.api.recipes.Recipe; +import gregtech.api.recipes.RecipeContext; import gregtech.api.recipes.RecipeMap; import gregtech.api.unification.OreDictUnifier; import gregtech.api.unification.ore.OrePrefix; @@ -451,8 +452,9 @@ protected static void applyTieredHammerNoRandomDrops(@NotNull IBlockState blockS Recipe recipe = map.findRecipe(Long.MAX_VALUE, Collections.singletonList(itemStack), Collections.emptyList()); if (recipe != null && !recipe.getOutputs().isEmpty()) { drops.clear(); - for (ItemStack outputStack : recipe.getResultItemOutputs(GTUtility.getTierByVoltage(recipe.getEUt()), tier, - map)) { + var context = new RecipeContext() + .update(map.getChanceFunction(), GTUtility.getTierByVoltage(recipe.getEUt()), tier); + for (ItemStack outputStack : recipe.getResultItemOutputs(context)) { outputStack = outputStack.copy(); if (OreDictUnifier.getPrefix(outputStack) == OrePrefix.crushed) { if (fortuneLevel > 0) { diff --git a/src/main/java/gregtech/api/recipes/Recipe.java b/src/main/java/gregtech/api/recipes/Recipe.java index ff495651ac5..4913ee94d14 100644 --- a/src/main/java/gregtech/api/recipes/Recipe.java +++ b/src/main/java/gregtech/api/recipes/Recipe.java @@ -2,7 +2,6 @@ import gregtech.api.capability.IMultipleTankHandler; import gregtech.api.recipes.category.GTRecipeCategory; -import gregtech.api.recipes.chance.boost.ChanceBoostFunction; import gregtech.api.recipes.chance.output.ChancedOutputList; import gregtech.api.recipes.chance.output.ChancedOutputLogic; import gregtech.api.recipes.chance.output.impl.ChancedFluidOutput; @@ -18,7 +17,6 @@ import net.minecraft.item.ItemStack; import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.items.IItemHandlerModifiable; -import net.minecraftforge.items.ItemHandlerHelper; import net.minecraftforge.oredict.OreDictionary; import com.google.common.collect.ImmutableList; @@ -464,37 +462,34 @@ public List getOutputs() { * The Recipe should be trimmed by calling {@link Recipe#getItemAndChanceOutputs(int)} before calling this method, * if trimming is required. * - * @param recipeTier The Voltage Tier of the Recipe, used for chanced output calculation - * @param machineTier The Voltage Tier of the Machine, used for chanced output calculation - * @param recipeMap The RecipeMap that the recipe is being performed upon, used for chanced output calculation + * @param context Context containing machine and recipe tier, the boost function, and the chance cache * @return A list of all resulting ItemStacks from the recipe, after chance has been applied to any chanced outputs */ - public List getResultItemOutputs(int recipeTier, int machineTier, RecipeMap recipeMap) { + public List getResultItemOutputs(RecipeContext context) { List outputs = new ArrayList<>(getOutputs()); - ChanceBoostFunction function = recipeMap.getChanceFunction(); - List chancedOutputsList = getChancedOutputs().roll(function, recipeTier, machineTier); + var chancedOutputsList = getChancedOutputs().roll(context); if (chancedOutputsList == null) return outputs; Collection resultChanced = new ArrayList<>(); - for (ChancedItemOutput chancedOutput : chancedOutputsList) { - ItemStack stackToAdd = chancedOutput.getIngredient().copy(); - for (ItemStack stackInList : resultChanced) { - int insertable = stackInList.getMaxStackSize() - stackInList.getCount(); - if (insertable > 0 && ItemHandlerHelper.canItemStacksStack(stackInList, stackToAdd)) { - if (insertable >= stackToAdd.getCount()) { - stackInList.grow(stackToAdd.getCount()); - stackToAdd = ItemStack.EMPTY; - break; - } else { - stackInList.grow(insertable); - stackToAdd.shrink(insertable); - } - } - } - if (!stackToAdd.isEmpty()) { - resultChanced.add(stackToAdd); - } + for (var chancedOutput : chancedOutputsList) { + ItemStack stackToAdd = chancedOutput.createStack((output, count) -> GTUtility.copy(count, output)); + // for (ItemStack stackInList : resultChanced) { + // int insertable = stackInList.getMaxStackSize() - stackInList.getCount(); + // if (insertable > 0 && ItemHandlerHelper.canItemStacksStack(stackInList, stackToAdd)) { + // if (insertable >= stackToAdd.getCount()) { + // stackInList.grow(stackToAdd.getCount()); + // stackToAdd = ItemStack.EMPTY; + // break; + // } else { + // stackInList.grow(insertable); + // stackToAdd.shrink(insertable); + // } + // } + // } + // if (!stackToAdd.isEmpty()) { + // } + resultChanced.add(stackToAdd); } outputs.addAll(resultChanced); @@ -659,22 +654,19 @@ public List getAllFluidOutputs() { * The Recipe should be trimmed by calling {@link Recipe#getFluidAndChanceOutputs(int)} before calling this method, * if trimming is required. * - * @param recipeTier The Voltage Tier of the Recipe, used for chanced output calculation - * @param machineTier The Voltage Tier of the Machine, used for chanced output calculation - * @param recipeMap The RecipeMap that the recipe is being performed upon, used for chanced output calculation + * @param context Context containing machine and recipe tier, the boost function, and the chance cache * @return A list of all resulting ItemStacks from the recipe, after chance has been applied to any chanced outputs */ - public List getResultFluidOutputs(int recipeTier, int machineTier, RecipeMap recipeMap) { + public List getResultFluidOutputs(RecipeContext context) { List outputs = new ArrayList<>(GTUtility.copyFluidList(getFluidOutputs())); - ChanceBoostFunction function = recipeMap.getChanceFunction(); - List chancedOutputsList = getChancedFluidOutputs().roll(function, recipeTier, machineTier); + var chancedOutputsList = getChancedFluidOutputs().roll(context); if (chancedOutputsList == null) return outputs; Collection resultChanced = new ArrayList<>(); - for (ChancedFluidOutput chancedOutput : chancedOutputsList) { - FluidStack stackToAdd = chancedOutput.getIngredient().copy(); + for (var chancedOutput : chancedOutputsList) { + FluidStack stackToAdd = chancedOutput.createStack(FluidStack::new); for (FluidStack stackInList : resultChanced) { int insertable = stackInList.amount; if (insertable > 0 && stackInList.getFluid() == stackToAdd.getFluid()) { diff --git a/src/main/java/gregtech/api/recipes/RecipeBuilder.java b/src/main/java/gregtech/api/recipes/RecipeBuilder.java index d12ec1afb90..bcbe97d6546 100644 --- a/src/main/java/gregtech/api/recipes/RecipeBuilder.java +++ b/src/main/java/gregtech/api/recipes/RecipeBuilder.java @@ -650,6 +650,68 @@ public R chancedOutput(MetaItem.MetaValueItem item, int chance, int tierChanc return chancedOutput(item, 1, chance, tierChanceBoost); } + public R chancedOutput(ItemStack stack, String fraction, int tierChanceBoost) { + if (stack == null || stack.isEmpty()) { + return (R) this; + } + + String[] split = fraction.split("/"); + if (split.length != 2) { + GTLog.logger.error("Fraction was not parsed correctly! Expected format is \"1/3\". Actual: \"{}\".", + fraction, new Throwable()); + recipeStatus = EnumValidationResult.INVALID; + return (R) this; + } + + int chance; + int maxChance; + try { + chance = Integer.parseInt(split[0]); + maxChance = Integer.parseInt(split[1]); + } catch (NumberFormatException e) { + GTLog.logger.error("Fraction was not parsed correctly! Expected format is \"1/3\". Actual: \"{}\".", + fraction, new Throwable()); + recipeStatus = EnumValidationResult.INVALID; + return (R) this; + } + + if (0 >= chance || chance > ChancedOutputLogic.getMaxChancedValue()) { + GTLog.logger.error("Chance cannot be less or equal to 0 or more than {}. Actual: {}.", + ChancedOutputLogic.getMaxChancedValue(), chance, new Throwable()); + recipeStatus = EnumValidationResult.INVALID; + return (R) this; + } + if (chance >= maxChance || maxChance > ChancedOutputLogic.getMaxChancedValue()) { + GTLog.logger.error("Max Chance cannot be less or equal to Chance or more than {}. Actual: {}.", + ChancedOutputLogic.getMaxChancedValue(), maxChance, new Throwable()); + recipeStatus = EnumValidationResult.INVALID; + return (R) this; + } + + int scalar = Math.floorDiv(ChancedOutputLogic.getMaxChancedValue(), maxChance); + chance *= scalar; + maxChance *= scalar; + + this.chancedOutputs.add(new ChancedItemOutput(stack.copy(), chance, maxChance, tierChanceBoost)); + return (R) this; + } + + public R chancedOutput(OrePrefix prefix, Material material, int count, String fraction, int tierChanceBoost) { + return chancedOutput(OreDictUnifier.get(prefix, material, count), fraction, tierChanceBoost); + } + + public R chancedOutput(OrePrefix prefix, Material material, String fraction, int tierChanceBoost) { + return chancedOutput(prefix, material, 1, fraction, tierChanceBoost); + } + + public R chancedOutput(MetaItem.MetaValueItem item, int count, String fraction, int tierChanceBoost) { + return chancedOutput(item.getStackForm(count), fraction, tierChanceBoost); + } + + public R chancedOutput(MetaItem.MetaValueItem item, String fraction, int tierChanceBoost) { + return chancedOutput(item, 1, fraction, tierChanceBoost); + } + public R chancedOutputs(List chancedOutputs) { for (ChancedItemOutput output : chancedOutputs) { this.chancedOutputs.add(output.copy()); @@ -681,6 +743,52 @@ public R chancedFluidOutput(FluidStack stack, int chance, int tierChanceBoost) { return (R) this; } + public R chancedFluidOutput(FluidStack stack, String fraction, int tierChanceBoost) { + if (stack == null || stack.amount == 0) { + return (R) this; + } + + String[] split = fraction.split("/"); + if (split.length != 2) { + GTLog.logger.error("Fraction was not parsed correctly! Expected format is \"1/3\". Actual: \"{}\".", + fraction, new Throwable()); + recipeStatus = EnumValidationResult.INVALID; + return (R) this; + } + + int chance; + int maxChance; + try { + chance = Integer.parseInt(split[0]); + maxChance = Integer.parseInt(split[1]); + } catch (NumberFormatException e) { + GTLog.logger.error("Fraction was not parsed correctly! Expected format is \"1/3\". Actual: \"{}\".", + fraction, new Throwable()); + recipeStatus = EnumValidationResult.INVALID; + return (R) this; + } + + if (0 >= chance || chance > ChancedOutputLogic.getMaxChancedValue()) { + GTLog.logger.error("Chance cannot be less or equal to 0 or more than {}. Actual: {}.", + ChancedOutputLogic.getMaxChancedValue(), chance, new Throwable()); + recipeStatus = EnumValidationResult.INVALID; + return (R) this; + } + if (chance >= maxChance || maxChance > ChancedOutputLogic.getMaxChancedValue()) { + GTLog.logger.error("Max Chance cannot be less or equal to Chance or more than {}. Actual: {}.", + ChancedOutputLogic.getMaxChancedValue(), maxChance, new Throwable()); + recipeStatus = EnumValidationResult.INVALID; + return (R) this; + } + + int scalar = Math.floorDiv(ChancedOutputLogic.getMaxChancedValue(), maxChance); + chance *= scalar; + maxChance *= scalar; + + this.chancedFluidOutputs.add(new ChancedFluidOutput(stack.copy(), chance, maxChance, tierChanceBoost)); + return (R) this; + } + public R chancedFluidOutputs(List chancedOutputs) { for (ChancedFluidOutput output : chancedOutputs) { this.chancedFluidOutputs.add(output.copy()); @@ -749,25 +857,25 @@ private static GTRecipeInput ofGroovyIngredient(IIngredient ingredient) { public void chancedOutputsMultiply(Recipe chancedOutputsFrom, int numberOfOperations) { for (ChancedItemOutput entry : chancedOutputsFrom.getChancedOutputs().getChancedEntries()) { - int chance = entry.getChance(); + String fraction = String.format("%d/%d", entry.getChance(), entry.getMaxChance()); int boost = entry.getChanceBoost(); // Add individual chanced outputs per number of parallel operations performed, to mimic regular recipes. // This is done instead of simply batching the chanced outputs by the number of parallel operations // performed for (int i = 0; i < numberOfOperations; i++) { - this.chancedOutput(entry.getIngredient().copy(), chance, boost); + this.chancedOutput(entry.getIngredient().copy(), fraction, boost); } } for (ChancedFluidOutput entry : chancedOutputsFrom.getChancedFluidOutputs().getChancedEntries()) { - int chance = entry.getChance(); + String fraction = String.format("%d/%d", entry.getChance(), entry.getMaxChance()); int boost = entry.getChanceBoost(); // Add individual chanced outputs per number of parallel operations performed, to mimic regular recipes. // This is done instead of simply batching the chanced outputs by the number of parallel operations // performed for (int i = 0; i < numberOfOperations; i++) { - this.chancedFluidOutput(entry.getIngredient().copy(), chance, boost); + this.chancedFluidOutput(entry.getIngredient().copy(), fraction, boost); } } } diff --git a/src/main/java/gregtech/api/recipes/RecipeContext.java b/src/main/java/gregtech/api/recipes/RecipeContext.java new file mode 100644 index 00000000000..64756eb805f --- /dev/null +++ b/src/main/java/gregtech/api/recipes/RecipeContext.java @@ -0,0 +1,67 @@ +package gregtech.api.recipes; + +import gregtech.api.recipes.chance.boost.BoostableChanceEntry; +import gregtech.api.recipes.chance.boost.ChanceBoostFunction; +import gregtech.api.recipes.chance.output.ChancedOutput; + +import it.unimi.dsi.fastutil.Hash; +import it.unimi.dsi.fastutil.objects.Object2IntOpenCustomHashMap; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; + +public class RecipeContext { + + private final Map cache; + ChanceBoostFunction boostFunction; + public int baseTier, machineTier; + + public RecipeContext(@Nullable Hash.Strategy strategy) { + this.cache = strategy == null ? null : new Object2IntOpenCustomHashMap<>(strategy); + } + + public RecipeContext() { + this(null); + } + + public RecipeContext update(ChanceBoostFunction boostFunction, + int baseTier, int machineTier) { + this.boostFunction = boostFunction; + this.baseTier = baseTier; + this.machineTier = machineTier; + return this; + } + + public void updateCachedChance(ChancedOutput entry, int chance) { + updateCachedChance(entry.getIngredient(), chance % entry.getMaxChance()); + } + + public void updateCachedChance(I ingredient, int chance) { + if (cache == null) return; + cache.put(ingredient, chance); + } + + public int getCachedChance(ChancedOutput entry) { + if (cache == null || !cache.containsKey(entry.getIngredient())) + return -1; + + return cache.get(entry.getIngredient()); + } + + public int getChance(ChancedOutput entry) { + int cache = getCachedChance(entry); + if (cache == -1) cache = 0; + if (entry instanceof BoostableChanceEntryboostableChanceEntry) { + return boostChance(boostableChanceEntry) + cache; + } + return entry.getChance() + cache; + } + + public int boostChance(BoostableChanceEntry entry) { + return boostFunction.getBoostedChance(entry, baseTier, machineTier); + } + + public Map getCache() { + return cache; + } +} diff --git a/src/main/java/gregtech/api/recipes/chance/BaseChanceEntry.java b/src/main/java/gregtech/api/recipes/chance/BaseChanceEntry.java deleted file mode 100644 index b1bcb49f0ce..00000000000 --- a/src/main/java/gregtech/api/recipes/chance/BaseChanceEntry.java +++ /dev/null @@ -1,29 +0,0 @@ -package gregtech.api.recipes.chance; - -import org.jetbrains.annotations.NotNull; - -/** - * Basic implementation for a chance entry. - * - * @param the type of ingredient contained by the chanced entry - */ -public abstract class BaseChanceEntry implements ChanceEntry { - - private final T ingredient; - private final int chance; - - public BaseChanceEntry(@NotNull T ingredient, int chance) { - this.ingredient = ingredient; - this.chance = chance; - } - - @Override - public @NotNull T getIngredient() { - return ingredient; - } - - @Override - public int getChance() { - return chance; - } -} diff --git a/src/main/java/gregtech/api/recipes/chance/ChanceEntry.java b/src/main/java/gregtech/api/recipes/chance/ChanceEntry.java index e5eeda1a875..dff3e68c954 100644 --- a/src/main/java/gregtech/api/recipes/chance/ChanceEntry.java +++ b/src/main/java/gregtech/api/recipes/chance/ChanceEntry.java @@ -20,6 +20,8 @@ public interface ChanceEntry { */ int getChance(); + int getMaxChance(); + /** * @return a copy of the chance entry */ diff --git a/src/main/java/gregtech/api/recipes/chance/output/BoostableChanceOutput.java b/src/main/java/gregtech/api/recipes/chance/output/BoostableChanceOutput.java index c7cadf8bc7c..3e9e4177092 100644 --- a/src/main/java/gregtech/api/recipes/chance/output/BoostableChanceOutput.java +++ b/src/main/java/gregtech/api/recipes/chance/output/BoostableChanceOutput.java @@ -14,8 +14,12 @@ public abstract class BoostableChanceOutput extends ChancedOutput implemen private final int chanceBoost; public BoostableChanceOutput(@NotNull T ingredient, int chance, int chanceBoost) { - super(ingredient, chance); - this.chanceBoost = chanceBoost; + this(ingredient, chance, ChancedOutputLogic.getMaxChancedValue(), chanceBoost); + } + + public BoostableChanceOutput(@NotNull T ingredient, int chance, int maxChance, int chanceBoost) { + super(ingredient, chance, maxChance); + this.chanceBoost = fixBoost(chanceBoost); } @Override @@ -23,11 +27,27 @@ public int getChanceBoost() { return this.chanceBoost; } + /** + * Attempts to fix and round the given chance boost due to potential differences + * between the max chance and {@link ChancedOutputLogic#getMaxChancedValue()}. + *
+ * The worst case would be {@code 5,001 / 10,000} , meaning the boost would + * have to be halved to have the intended effect. + * + * @param chanceBoost the chance boost to be fixed + * @return the fixed chance boost + */ + private int fixBoost(int chanceBoost) { + float error = (float) ChancedOutputLogic.getMaxChancedValue() / getMaxChance(); + return Math.round(chanceBoost / error); + } + @Override public String toString() { return "BoostableChanceOutput{" + "ingredient=" + getIngredient() + ", chance=" + getChance() + + ", maxChance=" + getMaxChance() + ", chanceBoost=" + getChanceBoost() + '}'; } diff --git a/src/main/java/gregtech/api/recipes/chance/output/CalculatedOutput.java b/src/main/java/gregtech/api/recipes/chance/output/CalculatedOutput.java new file mode 100644 index 00000000000..8a6c0c50177 --- /dev/null +++ b/src/main/java/gregtech/api/recipes/chance/output/CalculatedOutput.java @@ -0,0 +1,30 @@ +package gregtech.api.recipes.chance.output; + +public class CalculatedOutput { + + ChancedOutput output; + int amount; + + public CalculatedOutput(ChancedOutput output, int amount) { + this.output = output; + this.amount = amount; + } + + public CalculatedOutput(ChancedOutput output) { + this(output, 1); + } + + public I createStack(ChanceConverter converter) { + return converter.convert(output.getIngredient(), amount); + } + + public I getIngrediet() { + return output.getIngredient(); + } + + @FunctionalInterface + public interface ChanceConverter { + + I convert(I output, int count); + } +} diff --git a/src/main/java/gregtech/api/recipes/chance/output/ChancedOutput.java b/src/main/java/gregtech/api/recipes/chance/output/ChancedOutput.java index 9689fc1b208..8f8b2deee19 100644 --- a/src/main/java/gregtech/api/recipes/chance/output/ChancedOutput.java +++ b/src/main/java/gregtech/api/recipes/chance/output/ChancedOutput.java @@ -1,6 +1,6 @@ package gregtech.api.recipes.chance.output; -import gregtech.api.recipes.chance.BaseChanceEntry; +import gregtech.api.recipes.chance.ChanceEntry; import org.jetbrains.annotations.NotNull; @@ -9,9 +9,34 @@ * * @param the type of ingredient contained by the output */ -public abstract class ChancedOutput extends BaseChanceEntry { +public abstract class ChancedOutput implements ChanceEntry { + + private final T ingredient; + private final int chance; + private final int maxChance; public ChancedOutput(@NotNull T ingredient, int chance) { - super(ingredient, chance); + this(ingredient, chance, ChancedOutputLogic.getMaxChancedValue()); + } + + public ChancedOutput(@NotNull T ingredient, int chance, int maxChance) { + this.ingredient = ingredient; + this.chance = chance; + this.maxChance = maxChance; + } + + @Override + public @NotNull T getIngredient() { + return ingredient; + } + + @Override + public int getChance() { + return chance; + } + + @Override + public int getMaxChance() { + return maxChance; } } diff --git a/src/main/java/gregtech/api/recipes/chance/output/ChancedOutputList.java b/src/main/java/gregtech/api/recipes/chance/output/ChancedOutputList.java index 71856282127..3a2c0de1ba1 100644 --- a/src/main/java/gregtech/api/recipes/chance/output/ChancedOutputList.java +++ b/src/main/java/gregtech/api/recipes/chance/output/ChancedOutputList.java @@ -1,6 +1,6 @@ package gregtech.api.recipes.chance.output; -import gregtech.api.recipes.chance.boost.ChanceBoostFunction; +import gregtech.api.recipes.RecipeContext; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -33,14 +33,11 @@ public ChancedOutputList(@NotNull ChancedOutputLogic chancedOutputLogic, @NotNul /** * Roll the chances for this output list * - * @param boostFunction the function used to boost the outputs - * @param baseTier the base tier of the recipe - * @param machineTier the tier the recipe is run at + * @param context Context containing machine and recipe tier, the boost function, and the chance cache * @return a list of the rolled outputs */ - public @Nullable @Unmodifiable List roll(@NotNull ChanceBoostFunction boostFunction, int baseTier, - int machineTier) { - return chancedOutputLogic.roll(getChancedEntries(), boostFunction, baseTier, machineTier); + public @Nullable @Unmodifiable List> roll(@NotNull RecipeContext context) { + return chancedOutputLogic.roll(getChancedEntries(), context); } public @NotNull ChancedOutputLogic getChancedOutputLogic() { diff --git a/src/main/java/gregtech/api/recipes/chance/output/ChancedOutputLogic.java b/src/main/java/gregtech/api/recipes/chance/output/ChancedOutputLogic.java index 738d9176233..c4e7d1a1616 100644 --- a/src/main/java/gregtech/api/recipes/chance/output/ChancedOutputLogic.java +++ b/src/main/java/gregtech/api/recipes/chance/output/ChancedOutputLogic.java @@ -1,8 +1,7 @@ package gregtech.api.recipes.chance.output; import gregtech.api.GTValues; -import gregtech.api.recipes.chance.boost.BoostableChanceEntry; -import gregtech.api.recipes.chance.boost.ChanceBoostFunction; +import gregtech.api.recipes.RecipeContext; import com.google.common.collect.ImmutableList; import org.jetbrains.annotations.NotNull; @@ -23,18 +22,22 @@ public interface ChancedOutputLogic { ChancedOutputLogic OR = new ChancedOutputLogic() { @Override - public @Nullable @Unmodifiable > List<@NotNull T> roll(@NotNull @Unmodifiable List<@NotNull T> chancedEntries, - @NotNull ChanceBoostFunction boostFunction, - int baseTier, int machineTier) { - ImmutableList.Builder builder = null; + public @Nullable @Unmodifiable > List<@NotNull CalculatedOutput> roll( + @NotNull @Unmodifiable List<@NotNull T> chancedEntries, + @NotNull RecipeContext context) { + ImmutableList.Builder> builder = ImmutableList.builder(); + boolean success = false; + for (T entry : chancedEntries) { - if (passesChance(getChance(entry, boostFunction, baseTier, machineTier))) { - if (builder == null) builder = ImmutableList.builder(); - builder.add(entry); + int chance = context.getChance(entry); + if (passesChance(chance, entry, context)) { + success = true; + int amount = chance / entry.getMaxChance(); + builder.add(new CalculatedOutput<>(entry, amount)); } } - return builder == null ? null : builder.build(); + + return success ? builder.build() : null; } @Override @@ -54,16 +57,20 @@ public String toString() { ChancedOutputLogic AND = new ChancedOutputLogic() { @Override - public @Nullable @Unmodifiable > List<@NotNull T> roll(@NotNull @Unmodifiable List<@NotNull T> chancedEntries, - @NotNull ChanceBoostFunction boostFunction, - int baseTier, int machineTier) { + public @Nullable @Unmodifiable > List<@NotNull CalculatedOutput> roll( + @NotNull @Unmodifiable List<@NotNull T> chancedEntries, + @NotNull RecipeContext context) { + ImmutableList.Builder> builder = ImmutableList.builder(); + boolean failed = false; for (T entry : chancedEntries) { - if (!passesChance(getChance(entry, boostFunction, baseTier, machineTier))) { - return null; + int chance = context.getChance(entry); + int amount = chance / entry.getMaxChance(); + builder.add(new CalculatedOutput<>(entry, amount)); + if (!passesChance(chance, entry, context)) { + failed = true; } } - return ImmutableList.copyOf(chancedEntries); + return failed ? null : builder.build(); } @Override @@ -83,16 +90,26 @@ public String toString() { ChancedOutputLogic XOR = new ChancedOutputLogic() { @Override - public @Nullable @Unmodifiable > List<@NotNull T> roll(@NotNull @Unmodifiable List<@NotNull T> chancedEntries, - @NotNull ChanceBoostFunction boostFunction, - int baseTier, int machineTier) { + public @Nullable @Unmodifiable > List<@NotNull CalculatedOutput> roll( + @NotNull @Unmodifiable List<@NotNull T> chancedEntries, + @NotNull RecipeContext context) { + CalculatedOutput selected = null; + int total = 1; for (T entry : chancedEntries) { - if (passesChance(getChance(entry, boostFunction, baseTier, machineTier))) { - return Collections.singletonList(entry); + total += entry.getMaxChance(); + } + + int roll = GTValues.RNG.nextInt(total); + for (T entry : chancedEntries) { + int chance = context.getChance(entry); + if (chance >= roll && selected == null) { + selected = new CalculatedOutput<>(entry); + } else if (selected != null) { + roll -= chance; } + context.updateCachedChance(entry, chance); } - return null; + return selected == null ? null : Collections.singletonList(selected); } @Override @@ -112,10 +129,9 @@ public String toString() { ChancedOutputLogic NONE = new ChancedOutputLogic() { @Override - public @Nullable @Unmodifiable > List<@NotNull T> roll(@NotNull @Unmodifiable List<@NotNull T> chancedEntries, - @NotNull ChanceBoostFunction boostFunction, - int baseTier, int machineTier) { + public @Nullable @Unmodifiable > List<@NotNull CalculatedOutput> roll( + @NotNull @Unmodifiable List<@NotNull T> chancedEntries, + @NotNull RecipeContext context) { return null; } @@ -131,26 +147,22 @@ public String toString() { }; /** - * @param entry the entry to get the complete chance for - * @param boostFunction the function boosting the entry's chance - * @param baseTier the base tier of the recipe - * @param machineTier the tier the recipe is run at - * @return the total chance for the entry + * @param chance the boosted chance plus the cached chance to be checked + * @param entry the entry to get the max chance and compare to chance + * @param context Context containing machine and recipe tier, the boost function, and the chance cache + * @return if the roll with the chance is successful */ - static int getChance(@NotNull ChancedOutput entry, @NotNull ChanceBoostFunction boostFunction, int baseTier, - int machineTier) { - if (entry instanceof BoostableChanceEntryboostableChanceEntry) { - return boostFunction.getBoostedChance(boostableChanceEntry, baseTier, machineTier); + static > boolean passesChance(int chance, T entry, + @NotNull RecipeContext context) { + if (context.getCachedChance(entry) == -1) { + int initial = GTValues.RNG.nextInt(entry.getMaxChance()); + context.updateCachedChance(entry, initial); + // chance here is the boosted (if possible) entry chance + return initial <= chance; } - return entry.getChance(); - } - /** - * @param chance the chance to check - * @return if the roll with the chance is successful - */ - static boolean passesChance(int chance) { - return chance > 0 && GTValues.RNG.nextInt(getMaxChancedValue()) <= chance; + context.updateCachedChance(entry, chance); + return chance >= entry.getMaxChance(); } /** @@ -160,19 +172,9 @@ static int getMaxChancedValue() { return 10_000; } - /** - * Roll the chance and attempt to produce the output - * - * @param chancedEntries the list of entries to roll - * @param boostFunction the function to boost the entries' chances - * @param baseTier the base tier of the recipe - * @param machineTier the tier the recipe is run at - * @return a list of the produced outputs - */ - > @Nullable @Unmodifiable List<@NotNull T> roll( - @NotNull @Unmodifiable List<@NotNull T> chancedEntries, - @NotNull ChanceBoostFunction boostFunction, - int baseTier, int machineTier); + > @Nullable @Unmodifiable List<@NotNull CalculatedOutput> roll( + @NotNull @Unmodifiable List<@NotNull T> chancedEntries, + @NotNull RecipeContext context); @NotNull String getTranslationKey(); diff --git a/src/main/java/gregtech/api/recipes/chance/output/impl/ChancedFluidOutput.java b/src/main/java/gregtech/api/recipes/chance/output/impl/ChancedFluidOutput.java index 81a145fe1c8..21dcc6e96c0 100644 --- a/src/main/java/gregtech/api/recipes/chance/output/impl/ChancedFluidOutput.java +++ b/src/main/java/gregtech/api/recipes/chance/output/impl/ChancedFluidOutput.java @@ -15,9 +15,13 @@ public ChancedFluidOutput(@NotNull FluidStack ingredient, int chance, int chance super(ingredient, chance, chanceBoost); } + public ChancedFluidOutput(@NotNull FluidStack ingredient, int chance, int maxChance, int chanceBoost) { + super(ingredient, chance, maxChance, chanceBoost); + } + @Override public @NotNull ChancedFluidOutput copy() { - return new ChancedFluidOutput(getIngredient().copy(), getChance(), getChanceBoost()); + return new ChancedFluidOutput(getIngredient().copy(), getChance(), getMaxChance(), getChanceBoost()); } @Override diff --git a/src/main/java/gregtech/api/recipes/chance/output/impl/ChancedItemOutput.java b/src/main/java/gregtech/api/recipes/chance/output/impl/ChancedItemOutput.java index b49e640d942..99c3e03a1ed 100644 --- a/src/main/java/gregtech/api/recipes/chance/output/impl/ChancedItemOutput.java +++ b/src/main/java/gregtech/api/recipes/chance/output/impl/ChancedItemOutput.java @@ -16,9 +16,13 @@ public ChancedItemOutput(@NotNull ItemStack ingredient, int chance, int chanceBo super(ingredient, chance, chanceBoost); } + public ChancedItemOutput(@NotNull ItemStack ingredient, int chance, int maxChance, int chanceBoost) { + super(ingredient, chance, maxChance, chanceBoost); + } + @Override public @NotNull ChancedItemOutput copy() { - return new ChancedItemOutput(getIngredient().copy(), getChance(), getChanceBoost()); + return new ChancedItemOutput(getIngredient().copy(), getChance(), getMaxChance(), getChanceBoost()); } @Override diff --git a/src/main/java/gregtech/api/util/FluidStackHashStrategy.java b/src/main/java/gregtech/api/util/FluidStackHashStrategy.java new file mode 100644 index 00000000000..6ade524c978 --- /dev/null +++ b/src/main/java/gregtech/api/util/FluidStackHashStrategy.java @@ -0,0 +1,119 @@ +package gregtech.api.util; + +import net.minecraftforge.fluids.FluidStack; + +import it.unimi.dsi.fastutil.Hash; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; + +/** + * A configurable generator of hashing strategies, allowing for consideration of select properties of FluidStacks when + * considering equality. + */ +public interface FluidStackHashStrategy extends Hash.Strategy { + + /** + * @return a builder object for producing a custom FluidStackHashStrategy. + */ + static FluidStackHashStrategyBuilder builder() { + return new FluidStackHashStrategyBuilder(); + } + + /** + * Generates an FluidStackHash configured to compare every aspect of FluidStacks. + * + * @return the FluidStackHashStrategy as described above. + */ + static FluidStackHashStrategy comparingAll() { + return builder().compareFluid(true) + .compareAmount(true) + .compareTag(true) + .build(); + } + + /** + * Generates a FluidStackHash configured to compare every aspect of FluidStacks except the amount + * of fluid in the stack. + * + * @return the FluidStackHashStrategy as described above. + */ + static FluidStackHashStrategy comparingAllButAmount() { + return builder().compareFluid(true) + .compareTag(true) + .build(); + } + + static FluidStackHashStrategy comparingFluidAndAmount() { + return builder().compareFluid(true) + .compareAmount(true) + .build(); + } + + /** + * Builder pattern class for generating customized FluidStackHashStrategy + */ + class FluidStackHashStrategyBuilder { + + private boolean fluid, amount, tag; + + /** + * Defines whether the Fluid type should be considered for equality. + * + * @param choice {@code true} to consider this property, {@code false} to ignore it. + * @return {@code this} + */ + public FluidStackHashStrategyBuilder compareFluid(boolean choice) { + fluid = choice; + return this; + } + + /** + * Defines whether fluid amount should be considered for equality. + * + * @param choice {@code true} to consider this property, {@code false} to ignore it. + * @return {@code this} + */ + public FluidStackHashStrategyBuilder compareAmount(boolean choice) { + amount = choice; + return this; + } + + /** + * Defines whether NBT Tags should be considered for equality. + * + * @param choice {@code true} to consider this property, {@code false} to ignore it. + * @return {@code this} + */ + public FluidStackHashStrategyBuilder compareTag(boolean choice) { + tag = choice; + return this; + } + + /** + * @return the FluidStackHashStrategy as configured by "compare" methods. + */ + public FluidStackHashStrategy build() { + return new FluidStackHashStrategy() { + + @Override + public int hashCode(@Nullable FluidStack o) { + return o == null || o.amount == 0 ? 0 : Objects.hash( + fluid ? o.getFluid() : null, + amount ? o.amount : null, + tag ? o.tag : null); + } + + @Override + public boolean equals(@Nullable FluidStack a, @Nullable FluidStack b) { + if (a == null || a.amount == 0) return b == null || b.amount == 0; + if (b == null || b.amount == 0) return false; + + return (!fluid || a.getFluid() == b.getFluid()) && + (!amount || a.amount == b.amount) && + (!tag || Objects.equals(a.tag, b.tag)); + } + }; + } + } +} diff --git a/src/main/java/gregtech/api/util/TextFormattingUtil.java b/src/main/java/gregtech/api/util/TextFormattingUtil.java index 00a1f5f0aea..f01af12426c 100644 --- a/src/main/java/gregtech/api/util/TextFormattingUtil.java +++ b/src/main/java/gregtech/api/util/TextFormattingUtil.java @@ -55,6 +55,10 @@ public static String formatNumbers(double number) { return NUMBER_FORMAT.format(number); } + public static String formatPercent(double number) { + return String.format("%,.2f", number); + } + /** Allows for formatting Long, Integer, Short, Byte, Number, AtomicInteger, AtomicLong, and BigInteger. */ public static String formatNumbers(Object number) { return NUMBER_FORMAT.format(number); diff --git a/src/main/java/gregtech/integration/crafttweaker/recipe/CTRecipe.java b/src/main/java/gregtech/integration/crafttweaker/recipe/CTRecipe.java index ac71e2f0fbb..a524ce2802f 100644 --- a/src/main/java/gregtech/integration/crafttweaker/recipe/CTRecipe.java +++ b/src/main/java/gregtech/integration/crafttweaker/recipe/CTRecipe.java @@ -1,9 +1,12 @@ package gregtech.integration.crafttweaker.recipe; import gregtech.api.recipes.Recipe; +import gregtech.api.recipes.RecipeContext; import gregtech.api.recipes.RecipeMap; import gregtech.api.util.GTUtility; +import net.minecraft.item.ItemStack; + import crafttweaker.annotations.ZenRegister; import crafttweaker.api.item.IItemStack; import crafttweaker.api.liquid.ILiquidStack; @@ -49,7 +52,9 @@ public List getOutputs() { @ZenMethod public List getResultItemOutputs(@Optional(valueLong = 1) int tier) { - return this.backingRecipe.getResultItemOutputs(GTUtility.getTierByVoltage(getEUt()), tier, recipeMap) + return this.backingRecipe.getResultItemOutputs( + new RecipeContext() + .update(recipeMap.getChanceFunction(), GTUtility.getTierByVoltage(getEUt()), tier)) .stream() .map(MCItemStack::new) .collect(Collectors.toList()); diff --git a/src/main/java/gregtech/integration/jei/basic/OreByProduct.java b/src/main/java/gregtech/integration/jei/basic/OreByProduct.java index 8e062d069c0..56ed033c226 100755 --- a/src/main/java/gregtech/integration/jei/basic/OreByProduct.java +++ b/src/main/java/gregtech/integration/jei/basic/OreByProduct.java @@ -8,6 +8,7 @@ import gregtech.api.unification.material.properties.OreProperty; import gregtech.api.unification.material.properties.PropertyKey; import gregtech.api.unification.ore.OrePrefix; +import gregtech.api.util.TextFormattingUtil; import gregtech.client.utils.TooltipHelper; import gregtech.common.metatileentities.MetaTileEntities; @@ -287,9 +288,10 @@ public void getIngredients(IIngredients ingredients) { public void addTooltip(int slotIndex, boolean input, Object ingredient, List tooltip) { if (chances.containsKey(slotIndex)) { ChancedItemOutput entry = chances.get(slotIndex); - double chance = entry.getChance() / 100.0; + double chance = 100 * (double) entry.getChance() / entry.getMaxChance(); + String percent = TextFormattingUtil.formatPercent(chance); double boost = entry.getChanceBoost() / 100.0; - tooltip.add(TooltipHelper.BLINKING_CYAN + I18n.format("gregtech.recipe.chance", chance, boost)); + tooltip.add(TooltipHelper.BLINKING_CYAN + I18n.format("gregtech.recipe.chance", percent, boost)); } } diff --git a/src/main/java/gregtech/integration/jei/recipe/GTRecipeWrapper.java b/src/main/java/gregtech/integration/jei/recipe/GTRecipeWrapper.java index 4d88c1f1e5b..951407563bb 100644 --- a/src/main/java/gregtech/integration/jei/recipe/GTRecipeWrapper.java +++ b/src/main/java/gregtech/integration/jei/recipe/GTRecipeWrapper.java @@ -203,14 +203,15 @@ public void addIngredientTooltips(@NotNull Collection tooltip, boolean n @Nullable Object ingredient, @Nullable Object ingredient2) { if (ingredient2 instanceof ChancedOutputLogic logic) { if (ingredient instanceof BoostableChanceEntryentry) { - double chance = entry.getChance() / 100.0; + double chance = 100 * (double) entry.getChance() / entry.getMaxChance(); + String percent = TextFormattingUtil.formatPercent(chance); double boost = entry.getChanceBoost() / 100.0; if (logic != ChancedOutputLogic.NONE && logic != ChancedOutputLogic.OR) { tooltip.add(TooltipHelper.BLINKING_CYAN + I18n.format("gregtech.recipe.chance_logic", - chance, boost, I18n.format(logic.getTranslationKey()))); + percent, boost, I18n.format(logic.getTranslationKey()))); } else { tooltip.add(TooltipHelper.BLINKING_CYAN + I18n.format("gregtech.recipe.chance", - chance, boost)); + percent, boost)); } } } else if (notConsumed) { diff --git a/src/main/java/gregtech/loaders/recipe/MachineRecipeLoader.java b/src/main/java/gregtech/loaders/recipe/MachineRecipeLoader.java index 13606711c42..b10f3968d8a 100644 --- a/src/main/java/gregtech/loaders/recipe/MachineRecipeLoader.java +++ b/src/main/java/gregtech/loaders/recipe/MachineRecipeLoader.java @@ -969,7 +969,7 @@ private static void registerBlastFurnaceRecipes() { .input(ingot, Iron) .fluidInputs(Oxygen.getFluid(200)) .output(ingot, Steel) - .chancedOutput(dust, Ash, 1111, 0) + .chancedOutput(dust, Ash, "1/9", 0) .blastFurnaceTemp(1000) .buildAndRegister(); @@ -977,7 +977,7 @@ private static void registerBlastFurnaceRecipes() { .input(dust, Iron) .fluidInputs(Oxygen.getFluid(200)) .output(ingot, Steel) - .chancedOutput(dust, Ash, 1111, 0) + .chancedOutput(dust, Ash, "1/9", 0) .circuitMeta(2) .blastFurnaceTemp(1000) .buildAndRegister(); @@ -986,7 +986,7 @@ private static void registerBlastFurnaceRecipes() { .input(ingot, WroughtIron) .fluidInputs(Oxygen.getFluid(200)) .output(ingot, Steel) - .chancedOutput(dust, Ash, 1111, 0) + .chancedOutput(dust, Ash, "1/9", 0) .blastFurnaceTemp(1000) .buildAndRegister(); @@ -994,7 +994,7 @@ private static void registerBlastFurnaceRecipes() { .input(dust, WroughtIron) .fluidInputs(Oxygen.getFluid(200)) .output(ingot, Steel) - .chancedOutput(dust, Ash, 1111, 0) + .chancedOutput(dust, Ash, "1/9", 0) .circuitMeta(2) .blastFurnaceTemp(1000) .buildAndRegister(); @@ -1003,7 +1003,7 @@ private static void registerBlastFurnaceRecipes() { .input(dust, Iron, 4) .input(dust, Carbon) .output(ingot, Steel, 4) - .chancedOutput(dust, Ash, 1111, 0) + .chancedOutput(dust, Ash, "1/9", 0) .blastFurnaceTemp(2000) .buildAndRegister(); @@ -1011,19 +1011,19 @@ private static void registerBlastFurnaceRecipes() { .input(dust, WroughtIron, 4) .input(dust, Carbon) .output(ingot, Steel, 4) - .chancedOutput(dust, Ash, 1111, 0) + .chancedOutput(dust, Ash, "1/9", 0) .blastFurnaceTemp(2000) .buildAndRegister(); // Aluminium from aluminium oxide gems BLAST_RECIPES.recipeBuilder().duration(400).EUt(100).input(dust, Ruby).output(nugget, Aluminium, 3) - .chancedOutput(dust, Ash, 1111, 0).blastFurnaceTemp(1200).buildAndRegister(); + .chancedOutput(dust, Ash, "1/9", 0).blastFurnaceTemp(1200).buildAndRegister(); BLAST_RECIPES.recipeBuilder().duration(320).EUt(100).input(gem, Ruby).output(nugget, Aluminium, 3) - .chancedOutput(dust, Ash, 1111, 0).blastFurnaceTemp(1200).buildAndRegister(); + .chancedOutput(dust, Ash, "1/9", 0).blastFurnaceTemp(1200).buildAndRegister(); BLAST_RECIPES.recipeBuilder().duration(400).EUt(100).input(dust, GreenSapphire).output(nugget, Aluminium, 3) - .chancedOutput(dust, Ash, 1111, 0).blastFurnaceTemp(1200).buildAndRegister(); + .chancedOutput(dust, Ash, "1/9", 0).blastFurnaceTemp(1200).buildAndRegister(); BLAST_RECIPES.recipeBuilder().duration(320).EUt(100).input(gem, GreenSapphire).output(nugget, Aluminium, 3) - .chancedOutput(dust, Ash, 1111, 0).blastFurnaceTemp(1200).buildAndRegister(); + .chancedOutput(dust, Ash, "1/9", 0).blastFurnaceTemp(1200).buildAndRegister(); BLAST_RECIPES.recipeBuilder().duration(400).EUt(100).input(dust, Sapphire).output(nugget, Aluminium, 3) .blastFurnaceTemp(1200).buildAndRegister(); BLAST_RECIPES.recipeBuilder().duration(320).EUt(100).input(gem, Sapphire).output(nugget, Aluminium, 3) @@ -1084,7 +1084,7 @@ private static void registerBlastFurnaceMetallurgyRecipes() { .input(dust, SiliconDioxide, 3) .input(dust, Carbon, 2) .output(ingotHot, Silicon) - .chancedOutput(dust, Ash, 1111, 0) + .chancedOutput(dust, Ash, "1/9", 0) .fluidOutputs(CarbonMonoxide.getFluid(2000)) .buildAndRegister(); } @@ -1095,7 +1095,7 @@ private static void createSulfurDioxideRecipe(Material inputMaterial, Material o .input(dust, inputMaterial) .fluidInputs(Oxygen.getFluid(3000)) .output(dust, outputMaterial) - .chancedOutput(dust, Ash, 1111, 0) + .chancedOutput(dust, Ash, "1/9", 0) .fluidOutputs(SulfurDioxide.getFluid(sulfurDioxideAmount)) .buildAndRegister(); } @@ -1164,7 +1164,7 @@ private static void registerRecyclingRecipes() { MACERATOR_RECIPES.recipeBuilder() .input(stone, Soapstone) .output(dustImpure, Talc) - .chancedOutput(dust, Chromite, 111, 30) + .chancedOutput(dust, Chromite, "1/90", 30) .buildAndRegister(); if (!OreDictionary.getOres("stoneRedrock").isEmpty()) diff --git a/src/main/java/gregtech/loaders/recipe/MiscRecipeLoader.java b/src/main/java/gregtech/loaders/recipe/MiscRecipeLoader.java index fb3b3173dc4..cfdf9734078 100644 --- a/src/main/java/gregtech/loaders/recipe/MiscRecipeLoader.java +++ b/src/main/java/gregtech/loaders/recipe/MiscRecipeLoader.java @@ -80,7 +80,7 @@ public static void init() { .chancedOutput(gem, Flint, 9000, 0) .chancedOutput(gem, Flint, 8000, 0) .chancedOutput(gem, Flint, 6000, 0) - .chancedOutput(gem, Flint, 3300, 0) + .chancedOutput(gem, Flint, "1/3", 0) .chancedOutput(gem, Flint, 2500, 0) .buildAndRegister(); diff --git a/src/main/java/gregtech/loaders/recipe/chemistry/PetrochemRecipes.java b/src/main/java/gregtech/loaders/recipe/chemistry/PetrochemRecipes.java index abbf301bb49..f0f39731b1a 100644 --- a/src/main/java/gregtech/loaders/recipe/chemistry/PetrochemRecipes.java +++ b/src/main/java/gregtech/loaders/recipe/chemistry/PetrochemRecipes.java @@ -231,7 +231,7 @@ private static void distillationRecipes() { DISTILLATION_RECIPES.recipeBuilder() .fluidInputs(LightlySteamCrackedHeavyFuel.getFluid(1000)) - .chancedOutput(dust, Carbon, 1111, 0) + .chancedOutput(dust, Carbon, "1/9", 0) .fluidOutputs(LightFuel.getFluid(300)) .fluidOutputs(Naphtha.getFluid(50)) .fluidOutputs(Toluene.getFluid(25)) @@ -247,7 +247,7 @@ private static void distillationRecipes() { DISTILLATION_RECIPES.recipeBuilder() .fluidInputs(SeverelySteamCrackedHeavyFuel.getFluid(1000)) - .chancedOutput(dust, Carbon, 3333, 0) + .chancedOutput(dust, Carbon, "1/3", 0) .fluidOutputs(LightFuel.getFluid(100)) .fluidOutputs(Naphtha.getFluid(125)) .fluidOutputs(Toluene.getFluid(80)) @@ -283,7 +283,7 @@ private static void distillationRecipes() { DISTILLATION_RECIPES.recipeBuilder() .fluidInputs(LightlySteamCrackedLightFuel.getFluid(1000)) - .chancedOutput(dust, Carbon, 1111, 0) + .chancedOutput(dust, Carbon, "1/9", 0) .fluidOutputs(HeavyFuel.getFluid(150)) .fluidOutputs(Naphtha.getFluid(400)) .fluidOutputs(Toluene.getFluid(40)) @@ -299,7 +299,7 @@ private static void distillationRecipes() { DISTILLATION_RECIPES.recipeBuilder() .fluidInputs(SeverelySteamCrackedLightFuel.getFluid(1000)) - .chancedOutput(dust, Carbon, 3333, 0) + .chancedOutput(dust, Carbon, "1/3", 0) .fluidOutputs(HeavyFuel.getFluid(50)) .fluidOutputs(Naphtha.getFluid(100)) .fluidOutputs(Toluene.getFluid(30)) @@ -331,7 +331,7 @@ private static void distillationRecipes() { DISTILLATION_RECIPES.recipeBuilder() .fluidInputs(LightlySteamCrackedNaphtha.getFluid(1000)) - .chancedOutput(dust, Carbon, 1111, 0) + .chancedOutput(dust, Carbon, "1/9", 0) .fluidOutputs(HeavyFuel.getFluid(75)) .fluidOutputs(LightFuel.getFluid(150)) .fluidOutputs(Toluene.getFluid(40)) @@ -347,7 +347,7 @@ private static void distillationRecipes() { DISTILLATION_RECIPES.recipeBuilder() .fluidInputs(SeverelySteamCrackedNaphtha.getFluid(1000)) - .chancedOutput(dust, Carbon, 3333, 0) + .chancedOutput(dust, Carbon, "1/3", 0) .fluidOutputs(HeavyFuel.getFluid(25)) .fluidOutputs(LightFuel.getFluid(50)) .fluidOutputs(Toluene.getFluid(20)) @@ -377,7 +377,7 @@ private static void distillationRecipes() { DISTILLATION_RECIPES.recipeBuilder() .fluidInputs(LightlySteamCrackedGas.getFluid(1000)) - .chancedOutput(dust, Carbon, 1111, 0) + .chancedOutput(dust, Carbon, "1/9", 0) .fluidOutputs(Propene.getFluid(45)) .fluidOutputs(Ethane.getFluid(8)) .fluidOutputs(Ethylene.getFluid(85)) @@ -387,7 +387,7 @@ private static void distillationRecipes() { DISTILLATION_RECIPES.recipeBuilder() .fluidInputs(SeverelySteamCrackedGas.getFluid(1000)) - .chancedOutput(dust, Carbon, 1111, 0) + .chancedOutput(dust, Carbon, "1/9", 0) .fluidOutputs(Propene.getFluid(8)) .fluidOutputs(Ethane.getFluid(45)) .fluidOutputs(Ethylene.getFluid(92)) diff --git a/src/main/java/gregtech/loaders/recipe/chemistry/ReactorRecipes.java b/src/main/java/gregtech/loaders/recipe/chemistry/ReactorRecipes.java index 7178cadecbf..540bcf4b2e2 100644 --- a/src/main/java/gregtech/loaders/recipe/chemistry/ReactorRecipes.java +++ b/src/main/java/gregtech/loaders/recipe/chemistry/ReactorRecipes.java @@ -339,7 +339,7 @@ public static void init() { .circuitMeta(1) .input(gem, Charcoal) .fluidInputs(Oxygen.getFluid(1000)) - .chancedOutput(dust, Ash, 1111, 0) + .chancedOutput(dust, Ash, "1/9", 0) .fluidOutputs(CarbonMonoxide.getFluid(1000)) .duration(80).EUt(VA[ULV]).buildAndRegister(); @@ -347,7 +347,7 @@ public static void init() { .circuitMeta(1) .input(gem, Coal) .fluidInputs(Oxygen.getFluid(1000)) - .chancedOutput(dust, Ash, 1111, 0) + .chancedOutput(dust, Ash, "1/9", 0) .fluidOutputs(CarbonMonoxide.getFluid(1000)) .duration(80).EUt(VA[ULV]).buildAndRegister(); @@ -355,7 +355,7 @@ public static void init() { .circuitMeta(1) .input(dust, Charcoal) .fluidInputs(Oxygen.getFluid(1000)) - .chancedOutput(dust, Ash, 1111, 0) + .chancedOutput(dust, Ash, "1/9", 0) .fluidOutputs(CarbonMonoxide.getFluid(1000)) .duration(80).EUt(VA[ULV]).buildAndRegister(); @@ -364,7 +364,7 @@ public static void init() { .input(dust, Coal) .circuitMeta(1) .fluidInputs(Oxygen.getFluid(1000)) - .chancedOutput(dust, Ash, 1111, 0) + .chancedOutput(dust, Ash, "1/9", 0) .fluidOutputs(CarbonMonoxide.getFluid(1000)) .buildAndRegister(); @@ -409,7 +409,7 @@ public static void init() { .circuitMeta(2) .input(gem, Charcoal) .fluidInputs(Oxygen.getFluid(2000)) - .chancedOutput(dust, Ash, 1111, 0) + .chancedOutput(dust, Ash, "1/9", 0) .fluidOutputs(CarbonDioxide.getFluid(1000)) .duration(80).EUt(VA[ULV]).buildAndRegister(); @@ -417,7 +417,7 @@ public static void init() { .circuitMeta(2) .input(gem, Coal) .fluidInputs(Oxygen.getFluid(2000)) - .chancedOutput(dust, Ash, 1111, 0) + .chancedOutput(dust, Ash, "1/9", 0) .fluidOutputs(CarbonDioxide.getFluid(1000)) .duration(80).EUt(VA[ULV]).buildAndRegister(); @@ -425,7 +425,7 @@ public static void init() { .circuitMeta(2) .input(dust, Charcoal) .fluidInputs(Oxygen.getFluid(2000)) - .chancedOutput(dust, Ash, 1111, 0) + .chancedOutput(dust, Ash, "1/9", 0) .fluidOutputs(CarbonDioxide.getFluid(1000)) .duration(80).EUt(VA[ULV]).buildAndRegister(); @@ -433,7 +433,7 @@ public static void init() { .circuitMeta(2) .input(dust, Coal) .fluidInputs(Oxygen.getFluid(2000)) - .chancedOutput(dust, Ash, 1111, 0) + .chancedOutput(dust, Ash, "1/9", 0) .fluidOutputs(CarbonDioxide.getFluid(1000)) .duration(80).EUt(VA[ULV]).buildAndRegister(); diff --git a/src/main/java/gregtech/loaders/recipe/chemistry/SeparationRecipes.java b/src/main/java/gregtech/loaders/recipe/chemistry/SeparationRecipes.java index 59f27762319..c57c7544733 100644 --- a/src/main/java/gregtech/loaders/recipe/chemistry/SeparationRecipes.java +++ b/src/main/java/gregtech/loaders/recipe/chemistry/SeparationRecipes.java @@ -263,8 +263,8 @@ public static void init() { .input(dust, Stone) .chancedOutput(dust, Quartzite, 2500, 0) .chancedOutput(dust, PotassiumFeldspar, 2500, 0) - .chancedOutput(dust, Marble, 2222, 0) - .chancedOutput(dust, Biotite, 1111, 0) + .chancedOutput(dust, Marble, "2/9", 0) + .chancedOutput(dust, Biotite, "1/9", 0) .chancedOutput(dust, MetalMixture, 825, 80) .chancedOutput(dust, Sodalite, 550, 55) .buildAndRegister(); @@ -273,8 +273,8 @@ public static void init() { .input(dust, MetalMixture) .chancedOutput(dust, BandedIron, 2500, 0) .chancedOutput(dust, Bauxite, 2500, 0) - .chancedOutput(dust, Pyrolusite, 2222, 0) - .chancedOutput(dust, Barite, 1111, 0) + .chancedOutput(dust, Pyrolusite, "2/9", 0) + .chancedOutput(dust, Barite, "1/9", 0) .chancedOutput(dust, Chromite, 825, 80) .chancedOutput(dust, Ilmenite, 550, 55) .buildAndRegister(); diff --git a/src/main/java/gregtech/loaders/recipe/handlers/OreRecipeHandler.java b/src/main/java/gregtech/loaders/recipe/handlers/OreRecipeHandler.java index c31486610f2..b5bcfe58574 100644 --- a/src/main/java/gregtech/loaders/recipe/handlers/OreRecipeHandler.java +++ b/src/main/java/gregtech/loaders/recipe/handlers/OreRecipeHandler.java @@ -163,7 +163,7 @@ public static void processCrushedOre(OrePrefix crushedPrefix, Material material, .fluidInputs(Materials.Water.getFluid(1000)) .circuitMeta(1) .outputs(crushedPurifiedOre) - .chancedOutput(OrePrefix.dust, byproductMaterial, 3333, 0) + .chancedOutput(OrePrefix.dust, byproductMaterial, "1/3", 0) .output(OrePrefix.dust, Materials.Stone) .buildAndRegister(); @@ -171,7 +171,7 @@ public static void processCrushedOre(OrePrefix crushedPrefix, Material material, .input(crushedPrefix, material) .fluidInputs(Materials.DistilledWater.getFluid(100)) .outputs(crushedPurifiedOre) - .chancedOutput(OrePrefix.dust, byproductMaterial, 3333, 0) + .chancedOutput(OrePrefix.dust, byproductMaterial, "1/3", 0) .output(OrePrefix.dust, Materials.Stone) .duration(200) .buildAndRegister(); @@ -180,7 +180,7 @@ public static void processCrushedOre(OrePrefix crushedPrefix, Material material, .input(crushedPrefix, material) .outputs(crushedCentrifugedOre) .chancedOutput(OrePrefix.dust, property.getOreByProduct(1, material), property.getByProductMultiplier(), - 3333, 0) + "1/3", 0) .output(OrePrefix.dust, Materials.Stone) .buildAndRegister(); @@ -255,7 +255,7 @@ public static void processCrushedPurified(OrePrefix purifiedPrefix, Material mat RecipeMaps.THERMAL_CENTRIFUGE_RECIPES.recipeBuilder() .input(purifiedPrefix, material) .outputs(crushedCentrifugedStack) - .chancedOutput(OrePrefix.dust, byproductMaterial, 3333, 0) + .chancedOutput(OrePrefix.dust, byproductMaterial, "1/3", 0) .buildAndRegister(); } @@ -311,7 +311,7 @@ public static void processDirtyDust(OrePrefix dustPrefix, Material material, Ore .duration((int) (material.getMass() * 4)).EUt(24); if (byproduct.hasProperty(PropertyKey.DUST)) { - builder.chancedOutput(OrePrefix.dust, byproduct, 1111, 0); + builder.chancedOutput(OrePrefix.dust, byproduct, "1/9", 0); } else { builder.fluidOutputs(byproduct.getFluid(GTValues.L / 9)); } @@ -362,7 +362,7 @@ public static void processPureDust(OrePrefix purePrefix, Material material, OreP RecipeMaps.CENTRIFUGE_RECIPES.recipeBuilder() .input(purePrefix, material) .outputs(dustStack) - .chancedOutput(OrePrefix.dust, byproductMaterial, 1111, 0) + .chancedOutput(OrePrefix.dust, byproductMaterial, "1/9", 0) .duration(100) .EUt(5) .buildAndRegister(); diff --git a/src/main/resources/assets/gregtech/lang/en_us.lang b/src/main/resources/assets/gregtech/lang/en_us.lang index 389d68cd3ce..a6a6891c12e 100644 --- a/src/main/resources/assets/gregtech/lang/en_us.lang +++ b/src/main/resources/assets/gregtech/lang/en_us.lang @@ -5428,8 +5428,8 @@ gregtech.recipe.eu_inverted=Generation: %,d EU/t gregtech.recipe.duration=Duration: %s secs gregtech.recipe.amperage=Amperage: %,d gregtech.recipe.not_consumed=Does not get consumed in the process -gregtech.recipe.chance=Chance: %s%% +%s%%/tier -gregtech.recipe.chance_logic=Chance: %s%% +%s%%/tier (%s) +gregtech.recipe.chance=Chance: %s%% +%s%%/overclock +gregtech.recipe.chance_logic=Chance: %s%% +%s%%/overclock (%s) gregtech.chance_logic.or=OR gregtech.chance_logic.and=AND gregtech.chance_logic.xor=XOR diff --git a/src/test/java/gregtech/api/recipes/chance/output/ChancedOutputLogicTest.java b/src/test/java/gregtech/api/recipes/chance/output/ChancedOutputLogicTest.java index 4e6cbf026c9..c720d7a421e 100644 --- a/src/test/java/gregtech/api/recipes/chance/output/ChancedOutputLogicTest.java +++ b/src/test/java/gregtech/api/recipes/chance/output/ChancedOutputLogicTest.java @@ -1,5 +1,6 @@ package gregtech.api.recipes.chance.output; +import gregtech.api.recipes.RecipeContext; import gregtech.api.recipes.chance.ChanceEntry; import gregtech.api.recipes.chance.boost.ChanceBoostFunction; @@ -26,12 +27,15 @@ public TestChancedOutput(@NotNull String ingredient, int chance) { } } + private static final RecipeContext context = new RecipeContext().update(ChanceBoostFunction.NONE, 0, + 0); + private static > void listsMatch(@NotNull List original, - @Nullable List rolled) { + @Nullable List> rolled) { MatcherAssert.assertThat(rolled, CoreMatchers.notNullValue()); MatcherAssert.assertThat(rolled.size(), CoreMatchers.is(original.size())); for (int i = 0; i < original.size(); i++) { - MatcherAssert.assertThat(rolled.get(i).getIngredient(), CoreMatchers.is(original.get(i).getIngredient())); + MatcherAssert.assertThat(rolled.get(i).getIngrediet(), CoreMatchers.is(original.get(i).getIngredient())); } } @@ -42,7 +46,7 @@ public void testORLogic() { new TestChancedOutput("b", ChancedOutputLogic.getMaxChancedValue()), new TestChancedOutput("c", ChancedOutputLogic.getMaxChancedValue())); - List list = ChancedOutputLogic.OR.roll(chanceEntries, ChanceBoostFunction.NONE, 0, 0); + var list = ChancedOutputLogic.OR.roll(chanceEntries, context); listsMatch(chanceEntries, list); } @@ -53,7 +57,7 @@ public void testANDLogic() { new TestChancedOutput("b", ChancedOutputLogic.getMaxChancedValue()), new TestChancedOutput("c", 0)); - List list = ChancedOutputLogic.AND.roll(chanceEntries, ChanceBoostFunction.NONE, 0, 0); + var list = ChancedOutputLogic.AND.roll(chanceEntries, context); MatcherAssert.assertThat(list, CoreMatchers.nullValue()); chanceEntries = ImmutableList.of( @@ -61,7 +65,7 @@ public void testANDLogic() { new TestChancedOutput("b", ChancedOutputLogic.getMaxChancedValue()), new TestChancedOutput("c", ChancedOutputLogic.getMaxChancedValue())); - list = ChancedOutputLogic.AND.roll(chanceEntries, ChanceBoostFunction.NONE, 0, 0); + list = ChancedOutputLogic.AND.roll(chanceEntries, context); listsMatch(chanceEntries, list); } @@ -72,10 +76,19 @@ public void testXORLogic() { new TestChancedOutput("b", ChancedOutputLogic.getMaxChancedValue()), new TestChancedOutput("c", ChancedOutputLogic.getMaxChancedValue())); - List list = ChancedOutputLogic.XOR.roll(chanceEntries, ChanceBoostFunction.NONE, 0, 0); + var list = ChancedOutputLogic.XOR.roll(chanceEntries, context); MatcherAssert.assertThat(list, CoreMatchers.notNullValue()); MatcherAssert.assertThat(list.size(), CoreMatchers.is(1)); - MatcherAssert.assertThat(list.get(0).getIngredient(), CoreMatchers.is(chanceEntries.get(0).getIngredient())); + boolean exists = false; + for (var e : chanceEntries) { + if (e.getIngredient().equals(list.get(0).getIngrediet())) + exists = true; + } + MatcherAssert.assertThat(exists, CoreMatchers.is(true)); + + // XOR does not always produce the first entry from the list + // MatcherAssert.assertThat(list.get(0).output.getIngredient(), + // CoreMatchers.is(chanceEntries.get(0).getIngredient())); } @Test @@ -85,7 +98,7 @@ public void testNONELogic() { new TestChancedOutput("b", ChancedOutputLogic.getMaxChancedValue()), new TestChancedOutput("c", ChancedOutputLogic.getMaxChancedValue())); - List list = ChancedOutputLogic.NONE.roll(chanceEntries, ChanceBoostFunction.NONE, 0, 0); + var list = ChancedOutputLogic.NONE.roll(chanceEntries, context); MatcherAssert.assertThat(list, CoreMatchers.nullValue()); } }