Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make Output Chances Deterministic #2565

Open
wants to merge 40 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
91b39d5
initial rework
ghzdude Aug 1, 2024
3a1f7f1
a bit more work
ghzdude Aug 1, 2024
6ca403f
:todayiwill:
ghzdude Aug 1, 2024
a933cf1
apply to all logic types
ghzdude Aug 1, 2024
00e0b16
fix OR logic and handle overflow
ghzdude Aug 1, 2024
e0cf2d9
fix tests
ghzdude Aug 1, 2024
9056973
remove commented code
ghzdude Aug 1, 2024
f9fe1a6
allow null and null check
ghzdude Aug 1, 2024
58bc353
add default roll method
ghzdude Aug 1, 2024
a518618
improve XOR
ghzdude Aug 1, 2024
9781de2
not actually useful to seperate the fraction
ghzdude Aug 1, 2024
6f1ec05
forgor + less spots
ghzdude Aug 1, 2024
3832809
clear chance cache on new recipe
ghzdude Aug 1, 2024
3a8fdae
nvm we still need fraction split for handling irrational fractions
ghzdude Aug 3, 2024
0347b7e
more fixed to logic
ghzdude Aug 3, 2024
37969b1
add methods to RecipeBuilder
ghzdude Aug 3, 2024
c16a3b1
forgor fluids
ghzdude Aug 3, 2024
ce1984d
fix and improve RecipeBuilder methods
ghzdude Aug 3, 2024
0f2eecd
convert certain chances to fraction form
ghzdude Aug 3, 2024
446086e
fix copy
ghzdude Aug 3, 2024
0ee5e29
also apply to chanced fluids
ghzdude Aug 3, 2024
ad173f6
fix chances on tooltips
ghzdude Aug 3, 2024
72034a8
move cache clear after recipe is valid and checked
ghzdude Aug 3, 2024
ed7dcf7
move BaseChanceEntry up into ChancedOutput
ghzdude Aug 3, 2024
05e07f2
spotless
ghzdude Aug 3, 2024
d19491d
try to make chance boost work correctly
ghzdude Aug 3, 2024
b70494a
rip formatter
ghzdude Aug 3, 2024
fb305ee
move cache clear
ghzdude Aug 3, 2024
3c7f553
serialize chance cache
ghzdude Aug 3, 2024
e385415
add rng starting value for chance
ghzdude Aug 4, 2024
76bbca5
update cache getter method
ghzdude Aug 4, 2024
6f2d713
remove plus one + javadocs
ghzdude Aug 4, 2024
6ef3cc5
create and implement FluidStackHashStrategy
ghzdude Aug 4, 2024
027f5b9
try to correct boost w/ javadoc
ghzdude Aug 4, 2024
74295d6
fix todo
ghzdude Aug 4, 2024
d86ae1f
simplify roll by moving cache handling into `passesChance()`
ghzdude Aug 4, 2024
70c6700
remove while loop
ghzdude Aug 4, 2024
c5b748e
update chance lang
ghzdude Aug 4, 2024
f1275d9
simplify initial rng slightly
ghzdude Aug 5, 2024
b0715fd
try to make xor better
ghzdude Oct 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@
import gregtech.api.recipes.recipeproperties.CleanroomProperty;
import gregtech.api.recipes.recipeproperties.DimensionProperty;
import gregtech.api.recipes.recipeproperties.IRecipePropertyStorage;
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;
Expand All @@ -37,12 +39,14 @@
import net.minecraftforge.fluids.IFluidTank;
import net.minecraftforge.items.IItemHandlerModifiable;

import it.unimi.dsi.fastutil.objects.Object2IntOpenCustomHashMap;
import org.jetbrains.annotations.MustBeInvokedByOverriders;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import static gregtech.api.GTValues.ULV;
import static gregtech.api.recipes.logic.OverclockingLogic.*;
Expand Down Expand Up @@ -70,7 +74,16 @@ public abstract class AbstractRecipeLogic extends MTETrait implements IWorkable,
protected int maxProgressTime;
protected long recipeEUt;
protected List<FluidStack> fluidOutputs;
protected Map<FluidStack, Integer> fluidChancesCache = new Object2IntOpenCustomHashMap<>(
FluidStackHashStrategy.builder()
.compareFluid(true)
.build());
protected List<ItemStack> itemOutputs;
protected Map<ItemStack, Integer> itemChancesCache = new Object2IntOpenCustomHashMap<>(
ItemStackHashStrategy.builder()
.compareItem(true)
.compareDamage(true)
.build());

protected boolean isActive;
protected boolean workingEnabled = true;
Expand Down Expand Up @@ -390,6 +403,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.itemChancesCache.clear();
this.fluidChancesCache.clear();
}
this.previousRecipe = currentRecipe;
}
this.invalidInputsForRecipes = (currentRecipe == null);
Expand Down Expand Up @@ -947,9 +966,9 @@ protected void setupRecipe(@NotNull Recipe recipe) {
RecipeMap<?> map = getRecipeMap();
if (map != null) {
this.fluidOutputs = GTUtility
.copyFluidList(recipe.getResultFluidOutputs(recipeTier, machineTier, map));
.copyFluidList(recipe.getResultFluidOutputs(recipeTier, machineTier, map, fluidChancesCache));
this.itemOutputs = GTUtility
.copyStackList(recipe.getResultItemOutputs(recipeTier, machineTier, map));
.copyStackList(recipe.getResultItemOutputs(recipeTier, machineTier, map, itemChancesCache));
}

if (this.wasActiveAndNeedsUpdate) {
Expand Down Expand Up @@ -1209,6 +1228,21 @@ public NBTTagCompound serializeNBT() {
}
compound.setTag("ItemOutputs", itemOutputsList);
compound.setTag("FluidOutputs", fluidOutputsList);

NBTTagList itemCache = new NBTTagList();
for (var entry : itemChancesCache.entrySet()) {
var tag = entry.getKey().serializeNBT();
tag.setInteger("CachedChance", entry.getValue());
itemCache.appendTag(tag);
}
NBTTagList fluidCache = new NBTTagList();
for (var entry : fluidChancesCache.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;
}
Expand All @@ -1235,6 +1269,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.itemChancesCache.put(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.fluidChancesCache.put(FluidStack.loadFluidStackFromNBT(stack), cache);
}
}
}
}
15 changes: 11 additions & 4 deletions src/main/java/gregtech/api/recipes/Recipe.java
Original file line number Diff line number Diff line change
Expand Up @@ -470,10 +470,11 @@ public List<ItemStack> getOutputs() {
* @param recipeMap The RecipeMap that the recipe is being performed upon, used for chanced output calculation
* @return A list of all resulting ItemStacks from the recipe, after chance has been applied to any chanced outputs
*/
public List<ItemStack> getResultItemOutputs(int recipeTier, int machineTier, RecipeMap<?> recipeMap) {
public List<ItemStack> getResultItemOutputs(int recipeTier, int machineTier, RecipeMap<?> recipeMap,
Map<ItemStack, Integer> cache) {
List<ItemStack> outputs = new ArrayList<>(getOutputs());
ChanceBoostFunction function = recipeMap.getChanceFunction();
List<ChancedItemOutput> chancedOutputsList = getChancedOutputs().roll(function, recipeTier, machineTier);
List<ChancedItemOutput> chancedOutputsList = getChancedOutputs().roll(function, recipeTier, machineTier, cache);

if (chancedOutputsList == null) return outputs;

Expand Down Expand Up @@ -503,6 +504,10 @@ public List<ItemStack> getResultItemOutputs(int recipeTier, int machineTier, Rec
return outputs;
}

public List<ItemStack> getResultItemOutputs(int recipeTier, int machineTier, RecipeMap<?> recipeMap) {
return getResultItemOutputs(recipeTier, machineTier, recipeMap, null);
}

/**
* Returns the maximum possible recipe outputs from a recipe, divided into regular and chanced outputs
* Takes into account any specific output limiters, ie macerator slots, to trim down the output list
Expand Down Expand Up @@ -665,11 +670,13 @@ public List<FluidStack> getAllFluidOutputs() {
* @param recipeMap The RecipeMap that the recipe is being performed upon, used for chanced output calculation
* @return A list of all resulting ItemStacks from the recipe, after chance has been applied to any chanced outputs
*/
public List<FluidStack> getResultFluidOutputs(int recipeTier, int machineTier, RecipeMap<?> recipeMap) {
public List<FluidStack> getResultFluidOutputs(int recipeTier, int machineTier, RecipeMap<?> recipeMap,
Map<FluidStack, Integer> cache) {
List<FluidStack> outputs = new ArrayList<>(GTUtility.copyFluidList(getFluidOutputs()));

ChanceBoostFunction function = recipeMap.getChanceFunction();
List<ChancedFluidOutput> chancedOutputsList = getChancedFluidOutputs().roll(function, recipeTier, machineTier);
List<ChancedFluidOutput> chancedOutputsList = getChancedFluidOutputs().roll(function, recipeTier, machineTier,
cache);

if (chancedOutputsList == null) return outputs;

Expand Down
116 changes: 112 additions & 4 deletions src/main/java/gregtech/api/recipes/RecipeBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,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<ChancedItemOutput> chancedOutputs) {
for (ChancedItemOutput output : chancedOutputs) {
this.chancedOutputs.add(output.copy());
Expand Down Expand Up @@ -689,6 +751,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<ChancedFluidOutput> chancedOutputs) {
for (ChancedFluidOutput output : chancedOutputs) {
this.chancedFluidOutputs.add(output.copy());
Expand Down Expand Up @@ -757,25 +865,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);
}
}
}
Expand Down
29 changes: 0 additions & 29 deletions src/main/java/gregtech/api/recipes/chance/BaseChanceEntry.java

This file was deleted.

2 changes: 2 additions & 0 deletions src/main/java/gregtech/api/recipes/chance/ChanceEntry.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ public interface ChanceEntry<T> {
*/
int getChance();

int getMaxChance();

/**
* @return a copy of the chance entry
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,40 @@ public abstract class BoostableChanceOutput<T> extends ChancedOutput<T> 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
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()}.
* <br />
* 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() +
'}';
}
Expand Down
Loading