From 126f7121efaa8402dc518975a54a9c01f81540c8 Mon Sep 17 00:00:00 2001 From: Waiting Idly <25394029+WaitingIdly@users.noreply.github.com> Date: Sat, 6 Sep 2025 17:38:01 -0700 Subject: [PATCH 01/14] extract the hash strategy, rename field --- .../groovyscript/compat/vanilla/Furnace.java | 4 ++-- .../compat/vanilla/FurnaceRecipeManager.java | 23 +++++++------------ .../core/mixin/FurnaceRecipeMixin.java | 2 +- .../ingredient/ItemStackHashStrategy.java | 22 ++++++++++++++++++ 4 files changed, 33 insertions(+), 18 deletions(-) create mode 100644 src/main/java/com/cleanroommc/groovyscript/helper/ingredient/ItemStackHashStrategy.java diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/Furnace.java b/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/Furnace.java index f452d288d..a7184a24d 100644 --- a/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/Furnace.java +++ b/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/Furnace.java @@ -61,10 +61,10 @@ public boolean remove(Recipe recipe, boolean isScripted) { @GroovyBlacklist private ItemStack findTrueInput(ItemStack input) { - ItemStack trueInput = FurnaceRecipeManager.inputMap.get(input); + ItemStack trueInput = FurnaceRecipeManager.INPUT_SET.get(input); if (trueInput == null && input.getMetadata() != Short.MAX_VALUE) { input = new ItemStack(input.getItem(), input.getCount(), Short.MAX_VALUE); - trueInput = FurnaceRecipeManager.inputMap.get(input); + trueInput = FurnaceRecipeManager.INPUT_SET.get(input); } return trueInput; } diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/FurnaceRecipeManager.java b/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/FurnaceRecipeManager.java index 31c1f7a8a..ba9a1c5c2 100644 --- a/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/FurnaceRecipeManager.java +++ b/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/FurnaceRecipeManager.java @@ -1,26 +1,19 @@ package com.cleanroommc.groovyscript.compat.vanilla; import com.cleanroommc.groovyscript.api.GroovyBlacklist; -import it.unimi.dsi.fastutil.Hash; +import com.cleanroommc.groovyscript.helper.ingredient.ItemStackHashStrategy; import it.unimi.dsi.fastutil.objects.ObjectOpenCustomHashSet; import net.minecraft.item.ItemStack; -import net.minecraftforge.oredict.OreDictionary; - -import java.util.Objects; @GroovyBlacklist public class FurnaceRecipeManager { - public static final ObjectOpenCustomHashSet inputMap = new ObjectOpenCustomHashSet<>(new Hash.Strategy<>() { - - @Override - public int hashCode(ItemStack o) { - return Objects.hash(o.getItem(), o.getMetadata()); - } + /** + * All input items for the furnace. Uses a mixin so adding a recipe adds to this set. + * This does not control logic, it just reflects it. + * + * @see com.cleanroommc.groovyscript.core.mixin.FurnaceRecipeMixin FurnaceRecipeMixin + */ + public static final ObjectOpenCustomHashSet INPUT_SET = new ObjectOpenCustomHashSet<>(ItemStackHashStrategy.STRATEGY); - @Override - public boolean equals(ItemStack a, ItemStack b) { - return a == b || (a != null && b != null && OreDictionary.itemMatches(a, b, false)); - } - }); } diff --git a/src/main/java/com/cleanroommc/groovyscript/core/mixin/FurnaceRecipeMixin.java b/src/main/java/com/cleanroommc/groovyscript/core/mixin/FurnaceRecipeMixin.java index 85c5ef4ca..b77ba4ff6 100644 --- a/src/main/java/com/cleanroommc/groovyscript/core/mixin/FurnaceRecipeMixin.java +++ b/src/main/java/com/cleanroommc/groovyscript/core/mixin/FurnaceRecipeMixin.java @@ -13,6 +13,6 @@ public abstract class FurnaceRecipeMixin { @Inject(method = "addSmeltingRecipe", at = @At("RETURN")) public void addRecipe(ItemStack input, ItemStack stack, float experience, CallbackInfo ci) { - FurnaceRecipeManager.inputMap.add(input); + FurnaceRecipeManager.INPUT_SET.add(input); } } diff --git a/src/main/java/com/cleanroommc/groovyscript/helper/ingredient/ItemStackHashStrategy.java b/src/main/java/com/cleanroommc/groovyscript/helper/ingredient/ItemStackHashStrategy.java new file mode 100644 index 000000000..b3199b9e2 --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/helper/ingredient/ItemStackHashStrategy.java @@ -0,0 +1,22 @@ +package com.cleanroommc.groovyscript.helper.ingredient; + +import it.unimi.dsi.fastutil.Hash; +import net.minecraft.item.ItemStack; +import net.minecraftforge.oredict.OreDictionary; + +import java.util.Objects; + +public class ItemStackHashStrategy implements Hash.Strategy { + + public static final ItemStackHashStrategy STRATEGY = new ItemStackHashStrategy(); + + @Override + public int hashCode(ItemStack o) { + return Objects.hash(o.getItem(), o.getMetadata()); + } + + @Override + public boolean equals(ItemStack a, ItemStack b) { + return a == b || (a != null && b != null && OreDictionary.itemMatches(a, b, false)); + } +} From c3bf45c860104bac5d85835b1be99c041785616e Mon Sep 17 00:00:00 2001 From: Waiting Idly <25394029+WaitingIdly@users.noreply.github.com> Date: Sat, 6 Sep 2025 17:40:25 -0700 Subject: [PATCH 02/14] add custom time and fuel conversion --- examples/postInit/minecraft.groovy | 8 +- .../compat/vanilla/CustomFurnaceManager.java | 60 +++++++ .../groovyscript/compat/vanilla/Furnace.java | 146 +++++++++++++----- .../mixin/furnace/TileEntityFurnaceMixin.java | 49 ++++++ .../assets/groovyscript/lang/en_us.lang | 8 +- src/main/resources/mixin.groovyscript.json | 1 + 6 files changed, 234 insertions(+), 38 deletions(-) create mode 100644 src/main/java/com/cleanroommc/groovyscript/compat/vanilla/CustomFurnaceManager.java create mode 100644 src/main/java/com/cleanroommc/groovyscript/core/mixin/furnace/TileEntityFurnaceMixin.java diff --git a/examples/postInit/minecraft.groovy b/examples/postInit/minecraft.groovy index 717d841bc..b380ac348 100644 --- a/examples/postInit/minecraft.groovy +++ b/examples/postInit/minecraft.groovy @@ -159,12 +159,14 @@ mods.minecraft.crafting.shapelessBuilder() // mods.minecraft.crafting.replaceShapeless('minecraft:pink_dye_from_pink_tulp', item('minecraft:clay'), [item('minecraft:nether_star')]) // Furnace: -// Converts an input item into an output itemstack after a set amount of time, with the ability to give experience and -// using fuel to run. +// Converts an input item into an output itemstack after a configurable amount of time, with the ability to give experience +// and using fuel to run. Can also convert the item in the fuel slot. mods.minecraft.furnace.removeByInput(item('minecraft:clay')) mods.minecraft.furnace.removeByOutput(item('minecraft:brick')) +mods.minecraft.furnace.removeFuelConversionBySmelted(item('minecraft:sponge', 1)) // mods.minecraft.furnace.removeAll() +// mods.minecraft.furnace.removeAllFuelConversions() mods.minecraft.furnace.recipeBuilder() .input(ore('ingotGold')) @@ -175,6 +177,8 @@ mods.minecraft.furnace.recipeBuilder() // mods.minecraft.furnace.add(ore('ingotIron'), item('minecraft:diamond')) mods.minecraft.furnace.add(item('minecraft:nether_star'), item('minecraft:clay') * 64, 13) +mods.minecraft.furnace.add(item('minecraft:diamond'), item('minecraft:clay'), 2, 50) +mods.minecraft.furnace.addFuelConversion(item('minecraft:diamond'), item('minecraft:bucket').transform(item('minecraft:lava_bucket'))) // Default GameRules: // Create or assign a default value to GameRules. diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/CustomFurnaceManager.java b/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/CustomFurnaceManager.java new file mode 100644 index 000000000..91233d595 --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/CustomFurnaceManager.java @@ -0,0 +1,60 @@ +package com.cleanroommc.groovyscript.compat.vanilla; + +import com.cleanroommc.groovyscript.api.GroovyBlacklist; +import com.cleanroommc.groovyscript.api.IIngredient; +import com.cleanroommc.groovyscript.helper.ingredient.IngredientHelper; +import com.cleanroommc.groovyscript.helper.ingredient.ItemStackHashStrategy; +import com.github.bsideup.jabel.Desugar; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenCustomHashMap; +import net.minecraft.init.Blocks; +import net.minecraft.init.Items; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; + +import java.util.ArrayList; +import java.util.List; + +@GroovyBlacklist +public class CustomFurnaceManager { + + /** + * Time an itemstack takes to smelt. + *

+ * By default, minecraft uses 200 ticks for everything, GroovyScript uses a mixin to allow variable times to smelt. + * + * @see com.cleanroommc.groovyscript.core.mixin.furnace.TileEntityFurnaceMixin TileEntityFurnaceMixin + */ + public static final Object2IntMap TIME_MAP = new Object2IntOpenCustomHashMap<>(ItemStackHashStrategy.STRATEGY); + + /** + * Recipes for converting the fuel slot of a furnace into another item when a valid item is smelted. + *

+ * By default, minecraft has custom logic to make smelting a wet sponge convert an empty bucket into a water bucket. + * + * @see com.cleanroommc.groovyscript.core.mixin.furnace.TileEntityFurnaceMixin TileEntityFurnaceMixin + * @see FuelConversionRecipe + */ + public static final List FUEL_TRANSFORMERS = new ArrayList<>(); + + static { + // reproduce the vanilla logic for converting empty buckets into water buckets on smelting wet sponge + // in groovyscript this would be `furnace.addFuelConversion(item('minecraft:sponge', 1), item('minecraft:bucket').transform(item('minecraft:water_bucket')))` + var bucket = IngredientHelper.toIIngredient(((ItemStackMixinExpansion) (Object) (new ItemStack(Items.BUCKET))).transform(new ItemStack(Items.WATER_BUCKET))); + var wetSponge = IngredientHelper.toIIngredient(new ItemStack(Item.getItemFromBlock(Blocks.SPONGE), 1, 1)); + FUEL_TRANSFORMERS.add(new FuelConversionRecipe(wetSponge, bucket)); + } + + /** + * When the smelted ItemStack passes the {@link #smelted} filter and the {@link #fuel} filter, + * the {@link #fuel} IIngredient will use {@link IIngredient#applyTransform(ItemStack)} to convert the fuel stack. + * + * @param smelted an IIngredient that is checked against the item being smelted + * @param fuel an IIngredient that is checked against the fuel item, and if it passes uses {@link IIngredient#applyTransform(ItemStack)} to convert the fuel item. + */ + @Desugar + public record FuelConversionRecipe(IIngredient smelted, IIngredient fuel) { + + } + +} diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/Furnace.java b/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/Furnace.java index a7184a24d..014e9d5f2 100644 --- a/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/Furnace.java +++ b/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/Furnace.java @@ -7,6 +7,7 @@ import com.cleanroommc.groovyscript.helper.SimpleObjectStream; import com.cleanroommc.groovyscript.helper.ingredient.IngredientHelper; import com.cleanroommc.groovyscript.helper.recipe.AbstractRecipeBuilder; +import com.cleanroommc.groovyscript.registry.AbstractReloadableStorage; import com.cleanroommc.groovyscript.registry.VirtualizedRegistry; import net.minecraft.item.ItemStack; import net.minecraft.item.crafting.FurnaceRecipes; @@ -16,9 +17,16 @@ import java.util.List; import java.util.Map; -@RegistryDescription +@RegistryDescription( + admonition = @Admonition("groovyscript.wiki.minecraft.furnace.note0") +) public class Furnace extends VirtualizedRegistry { + private static final float EXPERIENCE_DEFAULT = 0.1f; + private static final int TIME_DEFAULT = 200; + + private final AbstractReloadableStorage conversionStorage = new AbstractReloadableStorage<>(); + @RecipeBuilderDescription(example = @Example(".input(ore('ingotGold')).output(item('minecraft:nether_star')).exp(0.5)")) public RecipeBuilder recipeBuilder() { return new RecipeBuilder(); @@ -26,31 +34,23 @@ public RecipeBuilder recipeBuilder() { @MethodDescription(type = MethodDescription.Type.ADDITION, description = "groovyscript.wiki.minecraft.furnace.add0", example = @Example(value = "ore('ingotIron'), item('minecraft:diamond')", commented = true)) public void add(IIngredient input, ItemStack output) { - add(input, output, 0.1f); + recipeBuilder().input(input).output(output).register(); } @MethodDescription(type = MethodDescription.Type.ADDITION, description = "groovyscript.wiki.minecraft.furnace.add1", example = @Example("item('minecraft:nether_star'), item('minecraft:clay') * 64, 13")) public void add(IIngredient input, ItemStack output, float exp) { - if (GroovyLog.msg("Error adding Minecraft Furnace recipe") - .add(IngredientHelper.isEmpty(input), () -> "Input must not be empty") - .add(IngredientHelper.isEmpty(output), () -> "Output must not be empty") - .add(IngredientHelper.overMaxSize(input, 1), () -> "Input size must be 1") - .error() - .postIfNotEmpty()) { - return; - } - if (exp < 0) { - exp = 0.1f; - } - output = output.copy(); - for (ItemStack itemStack : input.getMatchingStacks()) { - add(new Recipe(itemStack, output, exp)); - } + recipeBuilder().exp(exp).input(input).output(output).register(); + } + + @MethodDescription(type = MethodDescription.Type.ADDITION, description = "groovyscript.wiki.minecraft.furnace.add2", example = @Example("item('minecraft:diamond'), item('minecraft:clay'), 2, 50")) + public void add(IIngredient input, ItemStack output, float exp, int time) { + recipeBuilder().time(time).exp(exp).input(input).output(output).register(); } @GroovyBlacklist public void add(Recipe recipe) { FurnaceRecipes.instance().addSmeltingRecipe(recipe.input, recipe.output, recipe.exp); + CustomFurnaceManager.TIME_MAP.put(recipe.input, recipe.time); addScripted(recipe); } @@ -102,9 +102,7 @@ public boolean removeByInput(ItemStack input, boolean log, boolean isScripted) { } ItemStack output = FurnaceRecipes.instance().getSmeltingList().remove(trueInput); if (output != null) { - float exp = FurnaceRecipes.instance().getSmeltingExperience(output); - Recipe recipe = new Recipe(trueInput, output, exp); - if (isScripted) addBackup(recipe); + if (isScripted) addBackup(Recipe.of(trueInput, output)); return true; } else { if (log) { @@ -142,9 +140,7 @@ public boolean removeByOutput(IIngredient output, boolean log, boolean isScripte List recipesToRemove = new ArrayList<>(); for (Map.Entry entry : FurnaceRecipes.instance().getSmeltingList().entrySet()) { if (output.test(entry.getValue())) { - float exp = FurnaceRecipes.instance().getSmeltingExperience(entry.getValue()); - Recipe recipe = new Recipe(entry.getKey(), entry.getValue(), exp); - recipesToRemove.add(recipe); + recipesToRemove.add(Recipe.of(entry.getKey(), entry.getValue())); } } if (recipesToRemove.isEmpty()) { @@ -160,6 +156,7 @@ public boolean removeByOutput(IIngredient output, boolean log, boolean isScripte for (Recipe recipe : recipesToRemove) { if (isScripted) addBackup(recipe); FurnaceRecipes.instance().getSmeltingList().remove(recipe.input); + CustomFurnaceManager.TIME_MAP.remove(output); } return true; @@ -169,8 +166,7 @@ public boolean removeByOutput(IIngredient output, boolean log, boolean isScripte public SimpleObjectStream streamRecipes() { List recipes = new ArrayList<>(); for (Map.Entry entry : FurnaceRecipes.instance().getSmeltingList().entrySet()) { - float exp = FurnaceRecipes.instance().getSmeltingExperience(entry.getValue()); - recipes.add(new Recipe(entry.getKey(), entry.getValue(), exp)); + recipes.add(Recipe.of(entry.getKey(), entry.getValue())); } return new SimpleObjectStream<>(recipes, false).setRemover(recipe -> remove(recipe, true)); } @@ -178,18 +174,55 @@ public SimpleObjectStream streamRecipes() { @MethodDescription(priority = 2000, example = @Example(commented = true)) public void removeAll() { FurnaceRecipes.instance().getSmeltingList().entrySet().removeIf(entry -> { - float exp = FurnaceRecipes.instance().getSmeltingExperience(entry.getValue()); - Recipe recipe = new Recipe(entry.getKey(), entry.getValue(), exp); + Recipe recipe = Recipe.of(entry.getKey(), entry.getValue()); addBackup(recipe); return true; }); } + @MethodDescription(type = MethodDescription.Type.ADDITION, description = "groovyscript.wiki.add_to_list") + public boolean addFuelConversion(CustomFurnaceManager.FuelConversionRecipe recipe) { + CustomFurnaceManager.FUEL_TRANSFORMERS.add(recipe); + return conversionStorage.addScripted(recipe); + } + + @MethodDescription(description = "groovyscript.wiki.remove_from_list") + public boolean removeFuelConversion(CustomFurnaceManager.FuelConversionRecipe recipe) { + return CustomFurnaceManager.FUEL_TRANSFORMERS.remove(recipe) && conversionStorage.addBackup(recipe); + } + + @MethodDescription(type = MethodDescription.Type.ADDITION, example = @Example("item('minecraft:diamond'), item('minecraft:bucket').transform(item('minecraft:lava_bucket'))")) + public boolean addFuelConversion(IIngredient smelted, IIngredient fuel) { + return addFuelConversion(new CustomFurnaceManager.FuelConversionRecipe(smelted, fuel)); + } + + @MethodDescription(example = @Example("item('minecraft:sponge', 1)")) + public boolean removeFuelConversionBySmelted(ItemStack smelted) { + return CustomFurnaceManager.FUEL_TRANSFORMERS.removeIf(x -> x.smelted().test(smelted) && conversionStorage.addBackup(x)); + } + + @MethodDescription(type = MethodDescription.Type.QUERY, description = "groovyscript.wiki.streamRecipes") + public SimpleObjectStream streamFuelConversions() { + return new SimpleObjectStream<>(CustomFurnaceManager.FUEL_TRANSFORMERS).setRemover(this::removeFuelConversion); + } + + @MethodDescription(priority = 2000, example = @Example(commented = true)) + public void removeAllFuelConversions() { + CustomFurnaceManager.FUEL_TRANSFORMERS.removeIf(conversionStorage::addBackup); + } + @GroovyBlacklist @Override public void onReload() { - getScriptedRecipes().forEach(recipe -> remove(recipe, false)); - getBackupRecipes().forEach(recipe -> FurnaceRecipes.instance().addSmeltingRecipe(recipe.input, recipe.output, recipe.exp)); + getScriptedRecipes().forEach(recipe -> { + remove(recipe, false); + }); + getBackupRecipes().forEach(recipe -> { + FurnaceRecipes.instance().addSmeltingRecipe(recipe.input, recipe.output, recipe.exp); + CustomFurnaceManager.TIME_MAP.put(recipe.output, recipe.time); + }); + CustomFurnaceManager.FUEL_TRANSFORMERS.addAll(conversionStorage.restoreFromBackup()); + CustomFurnaceManager.FUEL_TRANSFORMERS.removeAll(conversionStorage.removeScripted()); } @Property(property = "input", comp = @Comp(eq = 1)) @@ -197,7 +230,9 @@ public void onReload() { public static class RecipeBuilder extends AbstractRecipeBuilder { @Property(comp = @Comp(gte = 0)) - private float exp = 0.1f; + private float exp = EXPERIENCE_DEFAULT; + @Property(comp = @Comp(gte = 1)) + private int time = TIME_DEFAULT; @RecipeBuilderMethodDescription public RecipeBuilder exp(float exp) { @@ -205,6 +240,18 @@ public RecipeBuilder exp(float exp) { return this; } + @RecipeBuilderMethodDescription(field = "exp") + public RecipeBuilder experience(float exp) { + this.exp = exp; + return this; + } + + @RecipeBuilderMethodDescription + public RecipeBuilder time(int time) { + this.time = time; + return this; + } + @Override public String getErrorMsg() { return "Error adding Minecraft Furnace recipe"; @@ -214,9 +261,8 @@ public String getErrorMsg() { public void validate(GroovyLog.Msg msg) { validateItems(msg, 1, 1, 1, 1); validateFluids(msg); - if (exp < 0) { - exp = 0.1f; - } + msg.add(exp < 0, "exp must be a float greater than or equal to 0, yet it was {}", exp); + msg.add(time <= 0, "time must be an integer greater than 1, yet it was {}", time); } @Override @@ -224,8 +270,9 @@ public void validate(GroovyLog.Msg msg) { public @Nullable Recipe register() { if (!validate()) return null; Recipe recipe = null; - for (ItemStack itemStack : input.get(0).getMatchingStacks()) { - recipe = new Recipe(itemStack, output.get(0), exp); + var out = output.get(0); + for (ItemStack input : input.get(0).getMatchingStacks()) { + recipe = new Recipe(input, out, exp, time); VanillaModule.furnace.add(recipe); } return recipe; @@ -237,11 +284,36 @@ public static class Recipe { private final ItemStack input; private final ItemStack output; private final float exp; + private final int time; + + private Recipe(ItemStack input, ItemStack output) { + this(input, output, EXPERIENCE_DEFAULT); + } private Recipe(ItemStack input, ItemStack output, float exp) { + this(input, output, exp, TIME_DEFAULT); + } + + private Recipe(ItemStack input, ItemStack output, float exp, int time) { this.input = input; this.output = output; this.exp = exp; + this.time = time; + } + + private static Recipe of(ItemStack input) { + ItemStack output = FurnaceRecipes.instance().getSmeltingList().get(input); + float exp = FurnaceRecipes.instance().getSmeltingExperience(output); + int time = CustomFurnaceManager.TIME_MAP.getInt(output); + if (time <= 0) time = TIME_DEFAULT; + return new Recipe(input, output, exp, time); + } + + private static Recipe of(ItemStack input, ItemStack output) { + float exp = FurnaceRecipes.instance().getSmeltingExperience(output); + int time = CustomFurnaceManager.TIME_MAP.getInt(output); + if (time <= 0) time = TIME_DEFAULT; + return new Recipe(input, output, exp, time); } public ItemStack getInput() { @@ -255,5 +327,9 @@ public ItemStack getOutput() { public float getExp() { return exp; } + + public int getTime() { + return time; + } } } diff --git a/src/main/java/com/cleanroommc/groovyscript/core/mixin/furnace/TileEntityFurnaceMixin.java b/src/main/java/com/cleanroommc/groovyscript/core/mixin/furnace/TileEntityFurnaceMixin.java new file mode 100644 index 000000000..cb2b3471e --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/core/mixin/furnace/TileEntityFurnaceMixin.java @@ -0,0 +1,49 @@ +package com.cleanroommc.groovyscript.core.mixin.furnace; + +import com.cleanroommc.groovyscript.compat.vanilla.CustomFurnaceManager; +import com.llamalad7.mixinextras.injector.ModifyReturnValue; +import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; +import com.llamalad7.mixinextras.sugar.Local; +import net.minecraft.item.ItemStack; +import net.minecraft.tileentity.TileEntityFurnace; +import net.minecraft.util.NonNullList; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(TileEntityFurnace.class) +public class TileEntityFurnaceMixin { + + @Shadow + public NonNullList furnaceItemStacks; + + @ModifyReturnValue(method = "getCookTime", at = @At("RETURN")) + private int groovy$customCookTime(int original, ItemStack stack) { + int time = CustomFurnaceManager.TIME_MAP.getInt(stack); + return time <= 0 ? original : time; + } + + /** + * Skip the default bucket -> water bucket conversion when smelting a wet sponge, + * as its logic is replaced by an entry in {@link CustomFurnaceManager#FUEL_TRANSFORMERS}. + */ + @WrapWithCondition(method = "smeltItem", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/NonNullList;set(ILjava/lang/Object;)Ljava/lang/Object;", ordinal = 1)) + private boolean groovy$skipNormalBucketReplacement(NonNullList instance, int p_set_1_, E p_set_2_) { + return false; + } + + @Inject(method = "smeltItem", at = @At(value = "INVOKE", target = "Lnet/minecraft/item/ItemStack;shrink(I)V")) + private void groovy$customFuelReplacement(CallbackInfo ci, @Local(ordinal = 0) ItemStack input) { + var fuel = furnaceItemStacks.get(1); + for (var fuelTransformer : CustomFurnaceManager.FUEL_TRANSFORMERS) { + if (!fuelTransformer.smelted().test(input)) continue; + if (!fuelTransformer.fuel().test(fuel)) continue; + var stack = fuelTransformer.fuel().applyTransform(fuel); + furnaceItemStacks.set(1, stack == null || stack.isEmpty() ? ItemStack.EMPTY : stack); + return; // we can only correctly do one transformation, so only the first transformer operates. + } + } + +} diff --git a/src/main/resources/assets/groovyscript/lang/en_us.lang b/src/main/resources/assets/groovyscript/lang/en_us.lang index 2e361f80e..7bbe4c2c4 100644 --- a/src/main/resources/assets/groovyscript/lang/en_us.lang +++ b/src/main/resources/assets/groovyscript/lang/en_us.lang @@ -148,10 +148,16 @@ groovyscript.wiki.minecraft.crafting.replaceShapeless0=Adds a shapeless recipe i groovyscript.wiki.minecraft.crafting.replaceShapeless1=Adds a shapeless recipe in the format `name`, `output`, `input` and removes the recipe matching the given name groovyscript.wiki.minecraft.furnace.title=Furnace -groovyscript.wiki.minecraft.furnace.description=Converts an input item into an output itemstack after a set amount of time, with the ability to give experience and using fuel to run. +groovyscript.wiki.minecraft.furnace.description=Converts an input item into an output itemstack after a configurable amount of time, with the ability to give experience and using fuel to run. Can also convert the item in the fuel slot. +groovyscript.wiki.minecraft.furnace.note0=Fuel Conversion Recipes may not function as desired in all furnaces - only the vanilla furnace has specific support. By default the only recipe reproduces the vanilla behavior of a wet sponge converting an empty bucket into a water bucket. groovyscript.wiki.minecraft.furnace.add0=Adds a recipe in the format `input`, `output` groovyscript.wiki.minecraft.furnace.add1=Adds a recipe in the format `input`, `output`, `experience` +groovyscript.wiki.minecraft.furnace.add2=Adds a recipe in the format `input`, `output`, `experience`, `time` groovyscript.wiki.minecraft.furnace.exp.value=Sets the experience rewarded for smelting the given input +groovyscript.wiki.minecraft.furnace.time.value=Sets the time in ticks the recipe takes +groovyscript.wiki.minecraft.furnace.addFuelConversion=Add a conversion recipe in the format `smelted`, `fuel`, with `fuel` using an IIngredient transformer +groovyscript.wiki.minecraft.furnace.removeFuelConversionBySmelted=Removes all conversion recipes with the given smelted item +groovyscript.wiki.minecraft.furnace.removeAllFuelConversions=Removes all conversion recipes groovyscript.wiki.minecraft.ore_dict.title=Ore Dictionary groovyscript.wiki.minecraft.ore_dict.description=Manipulate the Ore Dictionary and what itemstacks are part of what oredicts. diff --git a/src/main/resources/mixin.groovyscript.json b/src/main/resources/mixin.groovyscript.json index 1c1a8e98a..d27840c22 100644 --- a/src/main/resources/mixin.groovyscript.json +++ b/src/main/resources/mixin.groovyscript.json @@ -22,6 +22,7 @@ "SlotCraftingAccess", "TileEntityPistonMixin", "VillagerProfessionAccessor", + "furnace.TileEntityFurnaceMixin", "groovy.AsmDecompilerMixin", "groovy.ClassNodeResolverMixin", "groovy.ClosureMixin", From 251a02e32b5b26bad512b9d96493d0b49460f6e5 Mon Sep 17 00:00:00 2001 From: Waiting Idly <25394029+WaitingIdly@users.noreply.github.com> Date: Sat, 6 Sep 2025 19:35:55 -0700 Subject: [PATCH 03/14] add defaultValue documentation for furnace --- .../com/cleanroommc/groovyscript/compat/vanilla/Furnace.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/Furnace.java b/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/Furnace.java index 014e9d5f2..f23a221af 100644 --- a/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/Furnace.java +++ b/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/Furnace.java @@ -229,9 +229,9 @@ public void onReload() { @Property(property = "output", comp = @Comp(eq = 1)) public static class RecipeBuilder extends AbstractRecipeBuilder { - @Property(comp = @Comp(gte = 0)) + @Property(comp = @Comp(gte = 0), defaultValue = "0.1f") private float exp = EXPERIENCE_DEFAULT; - @Property(comp = @Comp(gte = 1)) + @Property(comp = @Comp(gte = 1), defaultValue = "200") private int time = TIME_DEFAULT; @RecipeBuilderMethodDescription From 5cfd3a3f890b4541a6fc5b25f301106aebb1af72 Mon Sep 17 00:00:00 2001 From: Waiting Idly <25394029+WaitingIdly@users.noreply.github.com> Date: Sat, 6 Sep 2025 19:38:52 -0700 Subject: [PATCH 04/14] remove unused method --- .../cleanroommc/groovyscript/compat/vanilla/Furnace.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/Furnace.java b/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/Furnace.java index f23a221af..b0f10e4ad 100644 --- a/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/Furnace.java +++ b/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/Furnace.java @@ -301,14 +301,6 @@ private Recipe(ItemStack input, ItemStack output, float exp, int time) { this.time = time; } - private static Recipe of(ItemStack input) { - ItemStack output = FurnaceRecipes.instance().getSmeltingList().get(input); - float exp = FurnaceRecipes.instance().getSmeltingExperience(output); - int time = CustomFurnaceManager.TIME_MAP.getInt(output); - if (time <= 0) time = TIME_DEFAULT; - return new Recipe(input, output, exp, time); - } - private static Recipe of(ItemStack input, ItemStack output) { float exp = FurnaceRecipes.instance().getSmeltingExperience(output); int time = CustomFurnaceManager.TIME_MAP.getInt(output); From 8d03e27bf11301adbebda21d0618db3e11234e18 Mon Sep 17 00:00:00 2001 From: Waiting Idly <25394029+WaitingIdly@users.noreply.github.com> Date: Sat, 6 Sep 2025 19:44:27 -0700 Subject: [PATCH 05/14] change reload logic for time --- .../groovyscript/compat/vanilla/Furnace.java | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/Furnace.java b/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/Furnace.java index b0f10e4ad..cbdb6f72b 100644 --- a/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/Furnace.java +++ b/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/Furnace.java @@ -214,13 +214,10 @@ public void removeAllFuelConversions() { @GroovyBlacklist @Override public void onReload() { - getScriptedRecipes().forEach(recipe -> { - remove(recipe, false); - }); - getBackupRecipes().forEach(recipe -> { - FurnaceRecipes.instance().addSmeltingRecipe(recipe.input, recipe.output, recipe.exp); - CustomFurnaceManager.TIME_MAP.put(recipe.output, recipe.time); - }); + // since time is entirely custom, it can be cleared directly + CustomFurnaceManager.TIME_MAP.clear(); + getScriptedRecipes().forEach(recipe -> remove(recipe, false)); + getBackupRecipes().forEach(recipe -> FurnaceRecipes.instance().addSmeltingRecipe(recipe.input, recipe.output, recipe.exp)); CustomFurnaceManager.FUEL_TRANSFORMERS.addAll(conversionStorage.restoreFromBackup()); CustomFurnaceManager.FUEL_TRANSFORMERS.removeAll(conversionStorage.removeScripted()); } From e776b9c35b11e1b6056ea3f3826777a6485c713d Mon Sep 17 00:00:00 2001 From: Waiting Idly <25394029+WaitingIdly@users.noreply.github.com> Date: Sun, 28 Sep 2025 19:16:05 -0700 Subject: [PATCH 06/14] create custom class for itemstack set logic --- .../itemstack/ItemStackHashStrategy.java | 20 +++++++++ .../ingredient/itemstack/ItemStackSet.java | 45 +++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 src/main/java/com/cleanroommc/groovyscript/helper/ingredient/itemstack/ItemStackHashStrategy.java create mode 100644 src/main/java/com/cleanroommc/groovyscript/helper/ingredient/itemstack/ItemStackSet.java diff --git a/src/main/java/com/cleanroommc/groovyscript/helper/ingredient/itemstack/ItemStackHashStrategy.java b/src/main/java/com/cleanroommc/groovyscript/helper/ingredient/itemstack/ItemStackHashStrategy.java new file mode 100644 index 000000000..65db0c5aa --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/helper/ingredient/itemstack/ItemStackHashStrategy.java @@ -0,0 +1,20 @@ +package com.cleanroommc.groovyscript.helper.ingredient.itemstack; + +import it.unimi.dsi.fastutil.Hash; +import net.minecraft.item.ItemStack; + +public class ItemStackHashStrategy implements Hash.Strategy { + + public static final ItemStackHashStrategy STRATEGY = new ItemStackHashStrategy(); + + @Override + public int hashCode(ItemStack o) { + return 31 * o.getItem().hashCode() + o.getMetadata(); + } + + @Override + public boolean equals(ItemStack a, ItemStack b) { + return a == b || (a != null && b != null && ItemStack.areItemsEqual(a, b)); + } +} + diff --git a/src/main/java/com/cleanroommc/groovyscript/helper/ingredient/itemstack/ItemStackSet.java b/src/main/java/com/cleanroommc/groovyscript/helper/ingredient/itemstack/ItemStackSet.java new file mode 100644 index 000000000..40433749e --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/helper/ingredient/itemstack/ItemStackSet.java @@ -0,0 +1,45 @@ +package com.cleanroommc.groovyscript.helper.ingredient.itemstack; + +import it.unimi.dsi.fastutil.objects.ObjectOpenCustomHashSet; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; + +import java.util.Set; + +/** + * Some Minecraft logic functions different when interacting with + * {@link ItemStack}s with metadata equal to {@link Short#MAX_VALUE}. + * This class handles this logic via two sets - + * one for if the {@link ItemStack} being checked has wildcard metadata, + * and the other for if it doesn't. + */ +public class ItemStackSet { + + private final Set wildcard = new ObjectOpenHashSet<>(); + private final Set metadata = new ObjectOpenCustomHashSet<>(ItemStackHashStrategy.STRATEGY); + + public boolean add(ItemStack k) { + if (k.getItemDamage() == Short.MAX_VALUE) return wildcard.add(k.getItem()); + return metadata.add(k); + } + + public boolean remove(ItemStack k) { + if (k.getItemDamage() == Short.MAX_VALUE) return wildcard.remove(k.getItem()); + return metadata.remove(k); + } + + public boolean contains(ItemStack k) { + if (k.getItemDamage() == Short.MAX_VALUE) return wildcard.contains(k.getItem()); + return metadata.contains(k); + } + + public boolean containsAsWildcard(ItemStack k) { + return wildcard.contains(k.getItem()); + } + + public void clear() { + wildcard.clear(); + metadata.clear(); + } +} From 7bd4fcb42098d43f4b5bbb8fa2f76921bb377538 Mon Sep 17 00:00:00 2001 From: Waiting Idly <25394029+WaitingIdly@users.noreply.github.com> Date: Sun, 28 Sep 2025 19:16:13 -0700 Subject: [PATCH 07/14] apply that class --- .../groovyscript/compat/vanilla/Furnace.java | 21 ++++++++++--------- .../compat/vanilla/FurnaceRecipeManager.java | 8 +++---- .../core/mixin/FurnaceRecipeMixin.java | 2 +- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/Furnace.java b/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/Furnace.java index cbdb6f72b..fa4121f37 100644 --- a/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/Furnace.java +++ b/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/Furnace.java @@ -27,6 +27,15 @@ public class Furnace extends VirtualizedRegistry { private final AbstractReloadableStorage conversionStorage = new AbstractReloadableStorage<>(); + @GroovyBlacklist + private static ItemStack findTrueInput(ItemStack input) { + if (input == null || input.isEmpty()) return null; + if (FurnaceRecipeManager.FURNACE_INPUTS.containsAsWildcard(input)) { + return new ItemStack(input.getItem(), input.getCount(), Short.MAX_VALUE); + } + return FurnaceRecipeManager.FURNACE_INPUTS.contains(input) ? input : null; + } + @RecipeBuilderDescription(example = @Example(".input(ore('ingotGold')).output(item('minecraft:nether_star')).exp(0.5)")) public RecipeBuilder recipeBuilder() { return new RecipeBuilder(); @@ -59,16 +68,6 @@ public boolean remove(Recipe recipe, boolean isScripted) { return removeByInput(recipe.input, isScripted, isScripted); } - @GroovyBlacklist - private ItemStack findTrueInput(ItemStack input) { - ItemStack trueInput = FurnaceRecipeManager.INPUT_SET.get(input); - if (trueInput == null && input.getMetadata() != Short.MAX_VALUE) { - input = new ItemStack(input.getItem(), input.getCount(), Short.MAX_VALUE); - trueInput = FurnaceRecipeManager.INPUT_SET.get(input); - } - return trueInput; - } - @MethodDescription(example = @Example("item('minecraft:clay')")) public boolean removeByInput(ItemStack input) { return removeByInput(input, true); @@ -103,6 +102,7 @@ public boolean removeByInput(ItemStack input, boolean log, boolean isScripted) { ItemStack output = FurnaceRecipes.instance().getSmeltingList().remove(trueInput); if (output != null) { if (isScripted) addBackup(Recipe.of(trueInput, output)); + FurnaceRecipeManager.FURNACE_INPUTS.remove(trueInput); return true; } else { if (log) { @@ -156,6 +156,7 @@ public boolean removeByOutput(IIngredient output, boolean log, boolean isScripte for (Recipe recipe : recipesToRemove) { if (isScripted) addBackup(recipe); FurnaceRecipes.instance().getSmeltingList().remove(recipe.input); + FurnaceRecipeManager.FURNACE_INPUTS.remove(recipe.input); CustomFurnaceManager.TIME_MAP.remove(output); } diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/FurnaceRecipeManager.java b/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/FurnaceRecipeManager.java index ba9a1c5c2..48f942cc6 100644 --- a/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/FurnaceRecipeManager.java +++ b/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/FurnaceRecipeManager.java @@ -1,9 +1,7 @@ package com.cleanroommc.groovyscript.compat.vanilla; import com.cleanroommc.groovyscript.api.GroovyBlacklist; -import com.cleanroommc.groovyscript.helper.ingredient.ItemStackHashStrategy; -import it.unimi.dsi.fastutil.objects.ObjectOpenCustomHashSet; -import net.minecraft.item.ItemStack; +import com.cleanroommc.groovyscript.helper.ingredient.itemstack.ItemStackSet; @GroovyBlacklist public class FurnaceRecipeManager { @@ -11,9 +9,11 @@ public class FurnaceRecipeManager { /** * All input items for the furnace. Uses a mixin so adding a recipe adds to this set. * This does not control logic, it just reflects it. + * Uses a custom datastructure {@link ItemStackSet} to handle the special case of wildcard metadata + * without violating Set hash <=> equals logic * * @see com.cleanroommc.groovyscript.core.mixin.FurnaceRecipeMixin FurnaceRecipeMixin */ - public static final ObjectOpenCustomHashSet INPUT_SET = new ObjectOpenCustomHashSet<>(ItemStackHashStrategy.STRATEGY); + public static final ItemStackSet FURNACE_INPUTS = new ItemStackSet(); } diff --git a/src/main/java/com/cleanroommc/groovyscript/core/mixin/FurnaceRecipeMixin.java b/src/main/java/com/cleanroommc/groovyscript/core/mixin/FurnaceRecipeMixin.java index b77ba4ff6..ffa02a3f6 100644 --- a/src/main/java/com/cleanroommc/groovyscript/core/mixin/FurnaceRecipeMixin.java +++ b/src/main/java/com/cleanroommc/groovyscript/core/mixin/FurnaceRecipeMixin.java @@ -13,6 +13,6 @@ public abstract class FurnaceRecipeMixin { @Inject(method = "addSmeltingRecipe", at = @At("RETURN")) public void addRecipe(ItemStack input, ItemStack stack, float experience, CallbackInfo ci) { - FurnaceRecipeManager.INPUT_SET.add(input); + FurnaceRecipeManager.FURNACE_INPUTS.add(input); } } From 38727245753977160969a0e09d6187a7a911d456 Mon Sep 17 00:00:00 2001 From: Waiting Idly <25394029+WaitingIdly@users.noreply.github.com> Date: Sun, 28 Sep 2025 19:16:45 -0700 Subject: [PATCH 08/14] fix inputs being added to set when invalid --- .../cleanroommc/groovyscript/core/mixin/FurnaceRecipeMixin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/cleanroommc/groovyscript/core/mixin/FurnaceRecipeMixin.java b/src/main/java/com/cleanroommc/groovyscript/core/mixin/FurnaceRecipeMixin.java index ffa02a3f6..c6a912423 100644 --- a/src/main/java/com/cleanroommc/groovyscript/core/mixin/FurnaceRecipeMixin.java +++ b/src/main/java/com/cleanroommc/groovyscript/core/mixin/FurnaceRecipeMixin.java @@ -11,7 +11,7 @@ @Mixin(value = FurnaceRecipes.class) public abstract class FurnaceRecipeMixin { - @Inject(method = "addSmeltingRecipe", at = @At("RETURN")) + @Inject(method = "addSmeltingRecipe", at = @At("TAIL")) public void addRecipe(ItemStack input, ItemStack stack, float experience, CallbackInfo ci) { FurnaceRecipeManager.FURNACE_INPUTS.add(input); } From 7c7c84b239288bd37ab132f773f4677757826a5f Mon Sep 17 00:00:00 2001 From: Waiting Idly <25394029+WaitingIdly@users.noreply.github.com> Date: Wed, 1 Oct 2025 15:17:37 -0700 Subject: [PATCH 09/14] adjust removal methods and dont modify metadata --- .../groovyscript/compat/vanilla/Furnace.java | 131 ++++++------------ .../compat/vanilla/FurnaceRecipeManager.java | 19 --- .../core/mixin/FurnaceRecipeMixin.java | 18 --- .../ingredient/itemstack/ItemStackSet.java | 45 ------ src/main/resources/mixin.groovyscript.json | 1 - 5 files changed, 41 insertions(+), 173 deletions(-) delete mode 100644 src/main/java/com/cleanroommc/groovyscript/compat/vanilla/FurnaceRecipeManager.java delete mode 100644 src/main/java/com/cleanroommc/groovyscript/core/mixin/FurnaceRecipeMixin.java delete mode 100644 src/main/java/com/cleanroommc/groovyscript/helper/ingredient/itemstack/ItemStackSet.java diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/Furnace.java b/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/Furnace.java index fa4121f37..526fc014f 100644 --- a/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/Furnace.java +++ b/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/Furnace.java @@ -11,11 +11,11 @@ import com.cleanroommc.groovyscript.registry.VirtualizedRegistry; import net.minecraft.item.ItemStack; import net.minecraft.item.crafting.FurnaceRecipes; +import net.minecraftforge.oredict.OreDictionary; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; -import java.util.Map; @RegistryDescription( admonition = @Admonition("groovyscript.wiki.minecraft.furnace.note0") @@ -27,13 +27,9 @@ public class Furnace extends VirtualizedRegistry { private final AbstractReloadableStorage conversionStorage = new AbstractReloadableStorage<>(); - @GroovyBlacklist - private static ItemStack findTrueInput(ItemStack input) { - if (input == null || input.isEmpty()) return null; - if (FurnaceRecipeManager.FURNACE_INPUTS.containsAsWildcard(input)) { - return new ItemStack(input.getItem(), input.getCount(), Short.MAX_VALUE); - } - return FurnaceRecipeManager.FURNACE_INPUTS.contains(input) ? input : null; + private static boolean isInSmeltingList(ItemStack stack) { + if (stack.getMetadata() == OreDictionary.WILDCARD_VALUE) return FurnaceRecipes.instance().getSmeltingList().get(stack) != null; + return FurnaceRecipes.instance().getSmeltingList().get(new ItemStack(stack.getItem(), 1, OreDictionary.WILDCARD_VALUE)) != null; } @RecipeBuilderDescription(example = @Example(".input(ore('ingotGold')).output(item('minecraft:nether_star')).exp(0.5)")) @@ -64,112 +60,67 @@ public void add(Recipe recipe) { } @GroovyBlacklist - public boolean remove(Recipe recipe, boolean isScripted) { - return removeByInput(recipe.input, isScripted, isScripted); + public boolean remove(Recipe recipe) { + FurnaceRecipes.instance().getSmeltingList().remove(recipe.input, recipe.output); + CustomFurnaceManager.TIME_MAP.remove(recipe.input); + addBackup(recipe); + return true; } @MethodDescription(example = @Example("item('minecraft:clay')")) - public boolean removeByInput(ItemStack input) { - return removeByInput(input, true); - } - - public boolean removeByInput(ItemStack input, boolean log) { - return removeByInput(input, log, true); - } - - @GroovyBlacklist - public boolean removeByInput(ItemStack input, boolean log, boolean isScripted) { - if (IngredientHelper.isEmpty(input)) { - if (log) { - GroovyLog.msg("Error adding Minecraft Furnace recipe") - .add(IngredientHelper.isEmpty(input), () -> "Input must not be empty") - .error() - .postIfNotEmpty(); - } + public boolean removeByInput(IIngredient input) { + if (GroovyLog.msg("Error adding Minecraft Furnace recipe") + .add(IngredientHelper.isEmpty(input), () -> "Input must not be empty") + .error() + .postIfNotEmpty()) { return false; } - - ItemStack trueInput = findTrueInput(input); - if (trueInput == null) { - if (log) { - GroovyLog.msg("Error removing Minecraft Furnace recipe") - .add("Can't find recipe for input " + input) - .error() - .post(); + if (FurnaceRecipes.instance().getSmeltingList().entrySet().removeIf(entry -> { + if (input.test(entry.getKey())) { + addBackup(Recipe.of(entry.getKey(), entry.getValue())); + return true; } return false; - } - ItemStack output = FurnaceRecipes.instance().getSmeltingList().remove(trueInput); - if (output != null) { - if (isScripted) addBackup(Recipe.of(trueInput, output)); - FurnaceRecipeManager.FURNACE_INPUTS.remove(trueInput); + })) { return true; - } else { - if (log) { - GroovyLog.msg("Error removing Minecraft Furnace recipe") - .add("Found input, but no output for " + input) - .error() - .post(); - } } - + GroovyLog.msg("Error removing Minecraft Furnace recipe") + .add("Can't find recipe for input " + input) + .add(((Object) input instanceof ItemStack is && isInSmeltingList(is)), "the furnace often uses wildcard itemstacks, and removing also requires using them - ie item('minecraft:log:*')") + .error() + .post(); return false; } @MethodDescription(example = @Example("item('minecraft:brick')")) public boolean removeByOutput(IIngredient output) { - return removeByOutput(output, true); - } - - public boolean removeByOutput(IIngredient output, boolean log) { - return removeByOutput(output, log, true); - } - - @GroovyBlacklist - public boolean removeByOutput(IIngredient output, boolean log, boolean isScripted) { - if (IngredientHelper.isEmpty(output)) { - if (log) { - GroovyLog.msg("Error adding Minecraft Furnace recipe") - .add(IngredientHelper.isEmpty(output), () -> "Output must not be empty") - .error() - .postIfNotEmpty(); - } + if (GroovyLog.msg("Error adding Minecraft Furnace recipe") + .add(IngredientHelper.isEmpty(output), () -> "Output must not be empty") + .error() + .postIfNotEmpty()) { return false; } - - List recipesToRemove = new ArrayList<>(); - for (Map.Entry entry : FurnaceRecipes.instance().getSmeltingList().entrySet()) { + if (FurnaceRecipes.instance().getSmeltingList().entrySet().removeIf(entry -> { if (output.test(entry.getValue())) { - recipesToRemove.add(Recipe.of(entry.getKey(), entry.getValue())); - } - } - if (recipesToRemove.isEmpty()) { - if (log) { - GroovyLog.msg("Error removing Minecraft Furnace recipe") - .add("Can't find recipe for output " + output) - .error() - .post(); + addBackup(Recipe.of(entry.getKey(), entry.getValue())); + return true; } return false; + })) { + return true; } - - for (Recipe recipe : recipesToRemove) { - if (isScripted) addBackup(recipe); - FurnaceRecipes.instance().getSmeltingList().remove(recipe.input); - FurnaceRecipeManager.FURNACE_INPUTS.remove(recipe.input); - CustomFurnaceManager.TIME_MAP.remove(output); - } - - return true; + GroovyLog.msg("Error removing Minecraft Furnace recipe") + .add("Can't find recipe for output " + output) + .error() + .post(); + return false; } @MethodDescription(type = MethodDescription.Type.QUERY) public SimpleObjectStream streamRecipes() { List recipes = new ArrayList<>(); - for (Map.Entry entry : FurnaceRecipes.instance().getSmeltingList().entrySet()) { - recipes.add(Recipe.of(entry.getKey(), entry.getValue())); - } - return new SimpleObjectStream<>(recipes, false).setRemover(recipe -> remove(recipe, true)); + FurnaceRecipes.instance().getSmeltingList().forEach((key, value) -> recipes.add(Recipe.of(key, value))); + return new SimpleObjectStream<>(recipes, false).setRemover(this::remove); } @MethodDescription(priority = 2000, example = @Example(commented = true)) @@ -217,7 +168,7 @@ public void removeAllFuelConversions() { public void onReload() { // since time is entirely custom, it can be cleared directly CustomFurnaceManager.TIME_MAP.clear(); - getScriptedRecipes().forEach(recipe -> remove(recipe, false)); + getScriptedRecipes().forEach(recipe -> FurnaceRecipes.instance().getSmeltingList().remove(recipe.input, recipe.output)); getBackupRecipes().forEach(recipe -> FurnaceRecipes.instance().addSmeltingRecipe(recipe.input, recipe.output, recipe.exp)); CustomFurnaceManager.FUEL_TRANSFORMERS.addAll(conversionStorage.restoreFromBackup()); CustomFurnaceManager.FUEL_TRANSFORMERS.removeAll(conversionStorage.removeScripted()); diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/FurnaceRecipeManager.java b/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/FurnaceRecipeManager.java deleted file mode 100644 index 48f942cc6..000000000 --- a/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/FurnaceRecipeManager.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.cleanroommc.groovyscript.compat.vanilla; - -import com.cleanroommc.groovyscript.api.GroovyBlacklist; -import com.cleanroommc.groovyscript.helper.ingredient.itemstack.ItemStackSet; - -@GroovyBlacklist -public class FurnaceRecipeManager { - - /** - * All input items for the furnace. Uses a mixin so adding a recipe adds to this set. - * This does not control logic, it just reflects it. - * Uses a custom datastructure {@link ItemStackSet} to handle the special case of wildcard metadata - * without violating Set hash <=> equals logic - * - * @see com.cleanroommc.groovyscript.core.mixin.FurnaceRecipeMixin FurnaceRecipeMixin - */ - public static final ItemStackSet FURNACE_INPUTS = new ItemStackSet(); - -} diff --git a/src/main/java/com/cleanroommc/groovyscript/core/mixin/FurnaceRecipeMixin.java b/src/main/java/com/cleanroommc/groovyscript/core/mixin/FurnaceRecipeMixin.java deleted file mode 100644 index c6a912423..000000000 --- a/src/main/java/com/cleanroommc/groovyscript/core/mixin/FurnaceRecipeMixin.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.cleanroommc.groovyscript.core.mixin; - -import com.cleanroommc.groovyscript.compat.vanilla.FurnaceRecipeManager; -import net.minecraft.item.ItemStack; -import net.minecraft.item.crafting.FurnaceRecipes; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -@Mixin(value = FurnaceRecipes.class) -public abstract class FurnaceRecipeMixin { - - @Inject(method = "addSmeltingRecipe", at = @At("TAIL")) - public void addRecipe(ItemStack input, ItemStack stack, float experience, CallbackInfo ci) { - FurnaceRecipeManager.FURNACE_INPUTS.add(input); - } -} diff --git a/src/main/java/com/cleanroommc/groovyscript/helper/ingredient/itemstack/ItemStackSet.java b/src/main/java/com/cleanroommc/groovyscript/helper/ingredient/itemstack/ItemStackSet.java deleted file mode 100644 index 40433749e..000000000 --- a/src/main/java/com/cleanroommc/groovyscript/helper/ingredient/itemstack/ItemStackSet.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.cleanroommc.groovyscript.helper.ingredient.itemstack; - -import it.unimi.dsi.fastutil.objects.ObjectOpenCustomHashSet; -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; -import net.minecraft.item.Item; -import net.minecraft.item.ItemStack; - -import java.util.Set; - -/** - * Some Minecraft logic functions different when interacting with - * {@link ItemStack}s with metadata equal to {@link Short#MAX_VALUE}. - * This class handles this logic via two sets - - * one for if the {@link ItemStack} being checked has wildcard metadata, - * and the other for if it doesn't. - */ -public class ItemStackSet { - - private final Set wildcard = new ObjectOpenHashSet<>(); - private final Set metadata = new ObjectOpenCustomHashSet<>(ItemStackHashStrategy.STRATEGY); - - public boolean add(ItemStack k) { - if (k.getItemDamage() == Short.MAX_VALUE) return wildcard.add(k.getItem()); - return metadata.add(k); - } - - public boolean remove(ItemStack k) { - if (k.getItemDamage() == Short.MAX_VALUE) return wildcard.remove(k.getItem()); - return metadata.remove(k); - } - - public boolean contains(ItemStack k) { - if (k.getItemDamage() == Short.MAX_VALUE) return wildcard.contains(k.getItem()); - return metadata.contains(k); - } - - public boolean containsAsWildcard(ItemStack k) { - return wildcard.contains(k.getItem()); - } - - public void clear() { - wildcard.clear(); - metadata.clear(); - } -} diff --git a/src/main/resources/mixin.groovyscript.json b/src/main/resources/mixin.groovyscript.json index d27840c22..09c8c8191 100644 --- a/src/main/resources/mixin.groovyscript.json +++ b/src/main/resources/mixin.groovyscript.json @@ -12,7 +12,6 @@ "EventBusMixin", "FluidStackMixin", "ForgeRegistryMixin", - "FurnaceRecipeMixin", "InventoryCraftingAccess", "ItemMixin", "ItemStackMixin", From 093357ee7599ffff4222d1f54ccee6b08f86d37e Mon Sep 17 00:00:00 2001 From: Waiting Idly <25394029+WaitingIdly@users.noreply.github.com> Date: Wed, 1 Oct 2025 15:46:00 -0700 Subject: [PATCH 10/14] move time to proxy map --- .../compat/vanilla/CustomFurnaceManager.java | 6 +-- .../groovyscript/compat/vanilla/Furnace.java | 2 +- .../itemstack/ItemStack2IntProxyMap.java | 47 +++++++++++++++++++ .../itemstack/ItemStackHashStrategy.java | 9 ++++ 4 files changed, 59 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/cleanroommc/groovyscript/helper/ingredient/itemstack/ItemStack2IntProxyMap.java diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/CustomFurnaceManager.java b/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/CustomFurnaceManager.java index 91233d595..e2c1ce1cd 100644 --- a/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/CustomFurnaceManager.java +++ b/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/CustomFurnaceManager.java @@ -3,10 +3,8 @@ import com.cleanroommc.groovyscript.api.GroovyBlacklist; import com.cleanroommc.groovyscript.api.IIngredient; import com.cleanroommc.groovyscript.helper.ingredient.IngredientHelper; -import com.cleanroommc.groovyscript.helper.ingredient.ItemStackHashStrategy; +import com.cleanroommc.groovyscript.helper.ingredient.itemstack.ItemStack2IntProxyMap; import com.github.bsideup.jabel.Desugar; -import it.unimi.dsi.fastutil.objects.Object2IntMap; -import it.unimi.dsi.fastutil.objects.Object2IntOpenCustomHashMap; import net.minecraft.init.Blocks; import net.minecraft.init.Items; import net.minecraft.item.Item; @@ -25,7 +23,7 @@ public class CustomFurnaceManager { * * @see com.cleanroommc.groovyscript.core.mixin.furnace.TileEntityFurnaceMixin TileEntityFurnaceMixin */ - public static final Object2IntMap TIME_MAP = new Object2IntOpenCustomHashMap<>(ItemStackHashStrategy.STRATEGY); + public static final ItemStack2IntProxyMap TIME_MAP = new ItemStack2IntProxyMap(); /** * Recipes for converting the fuel slot of a furnace into another item when a valid item is smelted. diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/Furnace.java b/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/Furnace.java index 526fc014f..38f2343d6 100644 --- a/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/Furnace.java +++ b/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/Furnace.java @@ -62,7 +62,7 @@ public void add(Recipe recipe) { @GroovyBlacklist public boolean remove(Recipe recipe) { FurnaceRecipes.instance().getSmeltingList().remove(recipe.input, recipe.output); - CustomFurnaceManager.TIME_MAP.remove(recipe.input); + CustomFurnaceManager.TIME_MAP.removeInt(recipe.input); addBackup(recipe); return true; } diff --git a/src/main/java/com/cleanroommc/groovyscript/helper/ingredient/itemstack/ItemStack2IntProxyMap.java b/src/main/java/com/cleanroommc/groovyscript/helper/ingredient/itemstack/ItemStack2IntProxyMap.java new file mode 100644 index 000000000..3190bfdb7 --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/helper/ingredient/itemstack/ItemStack2IntProxyMap.java @@ -0,0 +1,47 @@ +package com.cleanroommc.groovyscript.helper.ingredient.itemstack; + +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenCustomHashMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraftforge.oredict.OreDictionary; + + +/** + * Some Minecraft logic functions different when interacting with + * {@link ItemStack}s with metadata equal to {@link Short#MAX_VALUE} ({@link net.minecraftforge.oredict.OreDictionary#WILDCARD_VALUE}. + *

+ * This class handles this logic via two maps - + * {@link Object2IntOpenHashMap} and {@link Object2IntOpenCustomHashMap} (with the hash strategy being {@link ItemStackHashStrategy#STRATEGY}. + * The former is for if the {@link ItemStack} being checked has wildcard metadata, + * and the latter is for if it doesn't. + *

+ * This means that insertion inserts into one of two maps depending on metadata, + * and retrieval first checks the wildcard map before checking the metadata-specific map. + */ +public class ItemStack2IntProxyMap { + + private final Object2IntMap wildcard = new Object2IntOpenHashMap<>(); + private final Object2IntMap metadata = new Object2IntOpenCustomHashMap<>(ItemStackHashStrategy.STRATEGY); + + public int put(ItemStack key, int value) { + if (key.getItemDamage() == OreDictionary.WILDCARD_VALUE) return wildcard.put(key.getItem(), value); + return metadata.put(key, value); + } + + public int removeInt(ItemStack key) { + if (key.getItemDamage() == OreDictionary.WILDCARD_VALUE) return wildcard.removeInt(key.getItem()); + return metadata.removeInt(key); + } + + public int getInt(ItemStack key) { + if (wildcard.containsKey(key.getItem())) wildcard.get(key.getItem()); + return metadata.get(key); + } + + public void clear() { + wildcard.clear(); + metadata.clear(); + } +} diff --git a/src/main/java/com/cleanroommc/groovyscript/helper/ingredient/itemstack/ItemStackHashStrategy.java b/src/main/java/com/cleanroommc/groovyscript/helper/ingredient/itemstack/ItemStackHashStrategy.java index 65db0c5aa..28163104d 100644 --- a/src/main/java/com/cleanroommc/groovyscript/helper/ingredient/itemstack/ItemStackHashStrategy.java +++ b/src/main/java/com/cleanroommc/groovyscript/helper/ingredient/itemstack/ItemStackHashStrategy.java @@ -3,6 +3,15 @@ import it.unimi.dsi.fastutil.Hash; import net.minecraft.item.ItemStack; +/** + * Hash strategy for fastutils that checks item and metadata. + * Note that in many cases, metadata equal to {@link Short#MAX_VALUE} + * (aka {@link net.minecraftforge.oredict.OreDictionary#WILDCARD_VALUE OreDictionary.WILDCARD_VALUE}) + * has special logic, and will need to be handled separately from the other ItemStacks. + *
+ * This cannot be part of the hash strategy, as doing so would require + * violating {@link Object#hashCode()}. + */ public class ItemStackHashStrategy implements Hash.Strategy { public static final ItemStackHashStrategy STRATEGY = new ItemStackHashStrategy(); From 5bf3d1bf564ea40f7977f3daf822105b8a4c74bd Mon Sep 17 00:00:00 2001 From: Waiting Idly <25394029+WaitingIdly@users.noreply.github.com> Date: Wed, 1 Oct 2025 16:16:12 -0700 Subject: [PATCH 11/14] proxy to getInt --- .../helper/ingredient/itemstack/ItemStack2IntProxyMap.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/cleanroommc/groovyscript/helper/ingredient/itemstack/ItemStack2IntProxyMap.java b/src/main/java/com/cleanroommc/groovyscript/helper/ingredient/itemstack/ItemStack2IntProxyMap.java index 3190bfdb7..fa369387c 100644 --- a/src/main/java/com/cleanroommc/groovyscript/helper/ingredient/itemstack/ItemStack2IntProxyMap.java +++ b/src/main/java/com/cleanroommc/groovyscript/helper/ingredient/itemstack/ItemStack2IntProxyMap.java @@ -36,8 +36,8 @@ public int removeInt(ItemStack key) { } public int getInt(ItemStack key) { - if (wildcard.containsKey(key.getItem())) wildcard.get(key.getItem()); - return metadata.get(key); + if (wildcard.containsKey(key.getItem())) wildcard.getInt(key.getItem()); + return metadata.getInt(key); } public void clear() { From 437cfeea63b9be99584e0865779a82aad2c31f46 Mon Sep 17 00:00:00 2001 From: Waiting Idly <25394029+WaitingIdly@users.noreply.github.com> Date: Wed, 1 Oct 2025 16:16:23 -0700 Subject: [PATCH 12/14] adjust error message, fix example --- examples/postInit/minecraft.groovy | 2 +- .../groovyscript/compat/vanilla/Furnace.java | 22 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/examples/postInit/minecraft.groovy b/examples/postInit/minecraft.groovy index b380ac348..32936877d 100644 --- a/examples/postInit/minecraft.groovy +++ b/examples/postInit/minecraft.groovy @@ -162,7 +162,7 @@ mods.minecraft.crafting.shapelessBuilder() // Converts an input item into an output itemstack after a configurable amount of time, with the ability to give experience // and using fuel to run. Can also convert the item in the fuel slot. -mods.minecraft.furnace.removeByInput(item('minecraft:clay')) +mods.minecraft.furnace.removeByInput(item('minecraft:clay:*')) mods.minecraft.furnace.removeByOutput(item('minecraft:brick')) mods.minecraft.furnace.removeFuelConversionBySmelted(item('minecraft:sponge', 1)) // mods.minecraft.furnace.removeAll() diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/Furnace.java b/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/Furnace.java index 38f2343d6..2a123e18f 100644 --- a/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/Furnace.java +++ b/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/Furnace.java @@ -5,6 +5,7 @@ import com.cleanroommc.groovyscript.api.IIngredient; import com.cleanroommc.groovyscript.api.documentation.annotations.*; import com.cleanroommc.groovyscript.helper.SimpleObjectStream; +import com.cleanroommc.groovyscript.helper.ingredient.GroovyScriptCodeConverter; import com.cleanroommc.groovyscript.helper.ingredient.IngredientHelper; import com.cleanroommc.groovyscript.helper.recipe.AbstractRecipeBuilder; import com.cleanroommc.groovyscript.registry.AbstractReloadableStorage; @@ -27,11 +28,6 @@ public class Furnace extends VirtualizedRegistry { private final AbstractReloadableStorage conversionStorage = new AbstractReloadableStorage<>(); - private static boolean isInSmeltingList(ItemStack stack) { - if (stack.getMetadata() == OreDictionary.WILDCARD_VALUE) return FurnaceRecipes.instance().getSmeltingList().get(stack) != null; - return FurnaceRecipes.instance().getSmeltingList().get(new ItemStack(stack.getItem(), 1, OreDictionary.WILDCARD_VALUE)) != null; - } - @RecipeBuilderDescription(example = @Example(".input(ore('ingotGold')).output(item('minecraft:nether_star')).exp(0.5)")) public RecipeBuilder recipeBuilder() { return new RecipeBuilder(); @@ -67,7 +63,7 @@ public boolean remove(Recipe recipe) { return true; } - @MethodDescription(example = @Example("item('minecraft:clay')")) + @MethodDescription(example = @Example("item('minecraft:clay:*')")) public boolean removeByInput(IIngredient input) { if (GroovyLog.msg("Error adding Minecraft Furnace recipe") .add(IngredientHelper.isEmpty(input), () -> "Input must not be empty") @@ -84,11 +80,15 @@ public boolean removeByInput(IIngredient input) { })) { return true; } - GroovyLog.msg("Error removing Minecraft Furnace recipe") - .add("Can't find recipe for input " + input) - .add(((Object) input instanceof ItemStack is && isInSmeltingList(is)), "the furnace often uses wildcard itemstacks, and removing also requires using them - ie item('minecraft:log:*')") - .error() - .post(); + var log = GroovyLog.msg("Error removing Minecraft Furnace recipe").error(); + log.add("Can't find recipe for input " + input); + if ((Object) input instanceof ItemStack is && is.getMetadata() != OreDictionary.WILDCARD_VALUE) { + var wild = new ItemStack(is.getItem(), 1, OreDictionary.WILDCARD_VALUE); + if (!FurnaceRecipes.instance().getSmeltingResult(wild).isEmpty()) { + log.add("there was no input found for {}, but there was an input matching the wildcard itemstack {}", GroovyScriptCodeConverter.asGroovyCode(is, false), GroovyScriptCodeConverter.asGroovyCode(wild, false)); + } + } + log.post(); return false; } From 104533955a65a9d12d9303f409a5fb83a6ca503e Mon Sep 17 00:00:00 2001 From: Waiting Idly <25394029+WaitingIdly@users.noreply.github.com> Date: Wed, 1 Oct 2025 16:26:47 -0700 Subject: [PATCH 13/14] removeFuelConversionBySmeltedStack --- examples/postInit/minecraft.groovy | 2 +- .../com/cleanroommc/groovyscript/compat/vanilla/Furnace.java | 2 +- src/main/resources/assets/groovyscript/lang/en_us.lang | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/postInit/minecraft.groovy b/examples/postInit/minecraft.groovy index 32936877d..c929d7505 100644 --- a/examples/postInit/minecraft.groovy +++ b/examples/postInit/minecraft.groovy @@ -164,7 +164,7 @@ mods.minecraft.crafting.shapelessBuilder() mods.minecraft.furnace.removeByInput(item('minecraft:clay:*')) mods.minecraft.furnace.removeByOutput(item('minecraft:brick')) -mods.minecraft.furnace.removeFuelConversionBySmelted(item('minecraft:sponge', 1)) +mods.minecraft.furnace.removeFuelConversionBySmeltedStack(item('minecraft:sponge', 1)) // mods.minecraft.furnace.removeAll() // mods.minecraft.furnace.removeAllFuelConversions() diff --git a/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/Furnace.java b/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/Furnace.java index 3f0203dcc..a15f9c320 100644 --- a/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/Furnace.java +++ b/src/main/java/com/cleanroommc/groovyscript/compat/vanilla/Furnace.java @@ -149,7 +149,7 @@ public boolean addFuelConversion(IIngredient smelted, IIngredient fuel) { } @MethodDescription(example = @Example("item('minecraft:sponge', 1)")) - public boolean removeFuelConversionBySmelted(ItemStack smelted) { + public boolean removeFuelConversionBySmeltedStack(ItemStack smelted) { return CustomFurnaceManager.FUEL_TRANSFORMERS.removeIf(x -> x.smelted().test(smelted) && conversionStorage.addBackup(x)); } diff --git a/src/main/resources/assets/groovyscript/lang/en_us.lang b/src/main/resources/assets/groovyscript/lang/en_us.lang index 58fec3a53..eb13c1a67 100644 --- a/src/main/resources/assets/groovyscript/lang/en_us.lang +++ b/src/main/resources/assets/groovyscript/lang/en_us.lang @@ -156,7 +156,7 @@ groovyscript.wiki.minecraft.furnace.add2=Adds a recipe in the format `input`, `o groovyscript.wiki.minecraft.furnace.exp.value=Sets the experience rewarded for smelting the given input groovyscript.wiki.minecraft.furnace.time.value=Sets the time in ticks the recipe takes groovyscript.wiki.minecraft.furnace.addFuelConversion=Add a conversion recipe in the format `smelted`, `fuel`, with `fuel` using an IIngredient transformer -groovyscript.wiki.minecraft.furnace.removeFuelConversionBySmelted=Removes all conversion recipes with the given smelted item +groovyscript.wiki.minecraft.furnace.removeFuelConversionBySmeltedStack=Removes all conversion recipes with the given smelted item groovyscript.wiki.minecraft.furnace.removeAllFuelConversions=Removes all conversion recipes groovyscript.wiki.minecraft.ore_dict.title=Ore Dictionary From 5ccbee84dd874320616815640312cb3d43315ca9 Mon Sep 17 00:00:00 2001 From: Waiting Idly <25394029+WaitingIdly@users.noreply.github.com> Date: Sun, 5 Oct 2025 21:06:37 -0700 Subject: [PATCH 14/14] remove unused file --- .../ingredient/ItemStackHashStrategy.java | 22 ------------------- 1 file changed, 22 deletions(-) delete mode 100644 src/main/java/com/cleanroommc/groovyscript/helper/ingredient/ItemStackHashStrategy.java diff --git a/src/main/java/com/cleanroommc/groovyscript/helper/ingredient/ItemStackHashStrategy.java b/src/main/java/com/cleanroommc/groovyscript/helper/ingredient/ItemStackHashStrategy.java deleted file mode 100644 index b3199b9e2..000000000 --- a/src/main/java/com/cleanroommc/groovyscript/helper/ingredient/ItemStackHashStrategy.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.cleanroommc.groovyscript.helper.ingredient; - -import it.unimi.dsi.fastutil.Hash; -import net.minecraft.item.ItemStack; -import net.minecraftforge.oredict.OreDictionary; - -import java.util.Objects; - -public class ItemStackHashStrategy implements Hash.Strategy { - - public static final ItemStackHashStrategy STRATEGY = new ItemStackHashStrategy(); - - @Override - public int hashCode(ItemStack o) { - return Objects.hash(o.getItem(), o.getMetadata()); - } - - @Override - public boolean equals(ItemStack a, ItemStack b) { - return a == b || (a != null && b != null && OreDictionary.itemMatches(a, b, false)); - } -}