diff --git a/src/main/groovy-tests/customRecipeClassTests.groovy b/src/main/groovy-tests/craftingRecipeTests.groovy similarity index 53% rename from src/main/groovy-tests/customRecipeClassTests.groovy rename to src/main/groovy-tests/craftingRecipeTests.groovy index badcd7d1..40d1cf46 100644 --- a/src/main/groovy-tests/customRecipeClassTests.groovy +++ b/src/main/groovy-tests/craftingRecipeTests.groovy @@ -1,6 +1,11 @@ +import com.nomiceu.nomilabs.LabsValues import com.nomiceu.nomilabs.groovy.ShapedConversionRecipe import com.nomiceu.nomilabs.groovy.ShapedDummyRecipe +import com.nomiceu.nomilabs.groovy.SimpleIIngredient +import com.nomiceu.nomilabs.util.ItemMeta +import net.minecraft.item.ItemStack import net.minecraft.util.text.TextFormatting +import net.minecraftforge.fml.common.Loader import static com.nomiceu.nomilabs.groovy.GroovyHelpers.NBTClearingRecipeHelpers.* import static com.nomiceu.nomilabs.groovy.GroovyHelpers.TranslationHelpers.* @@ -43,17 +48,20 @@ crafting.shapedBuilder() // Examples: NBT Clearing // Note that provided OUTPUT must match the output item in ALL CASES for that recipe! // Simplest Form, Same Item for Input and Output -nbtClearingRecipe(item('storagedrawers:compdrawers')) +if (Loader.isModLoaded(LabsValues.STORAGE_DRAWERS_MODID)) + nbtClearingRecipe(item('storagedrawers:compdrawers')) // Different Item for Input and Output nbtClearingRecipe(item('forge:bucketfilled'), item('minecraft:bucket')) // Same Input/Output Item, Custom NBT Clearer -nbtClearingRecipe(item('storagedrawers:basicdrawers'), { - var tag = transferSubTags(it, 'material') // Transfer material, saved generated tag - tag = transferDrawerUpgradeData(it, tag) // Transfer Upgrades - it.tagCompound = tag // Remember to Save! -}) +if (Loader.isModLoaded(LabsValues.STORAGE_DRAWERS_MODID)) { + nbtClearingRecipe(item('storagedrawers:basicdrawers'), { + var tag = transferSubTags(it, 'material') // Transfer material, saved generated tag + tag = transferDrawerUpgradeData(it, tag) // Transfer Upgrades + it.tagCompound = tag // Remember to Save! + }) +} // Different Input/Output Item, Custom NBT Clearer, Custom CanClear/Warning Tooltips nbtClearingRecipe(item('minecraft:water_bucket'), item('minecraft:bucket'), @@ -64,3 +72,62 @@ nbtClearingRecipe(item('minecraft:water_bucket'), item('minecraft:bucket'), translatableLiteral('Warning: Great Water?').addFormat(TextFormatting.RED) ) +// Examples: Strict Recipes + +/** + * This means, in JEI, the list of 'matching stacks' will be displayed + * exactly as set, instead of expanding wildcards and removing duplicates. + */ +// Only JEI display is affected! This is only useful with custom IIngredients! + +// Custom IIngredient: Returns different NBTs of Stone (NBT's are auto-condensed by JEI) +class TestStone extends SimpleIIngredient { + + ItemStack stone = item('minecraft:stone') + + @Override + ItemStack[] getMatchingStacks() { + return [ + item('minecraft:stone'), + item('minecraft:stone').withNbt(['display': ['Name': 'Test']]), + item('minecraft:stone').withNbt(['display': ['Name': 'Test 2']]), + item('minecraft:stone').withNbt(['display': ['Name': 'Test 3']]), + ].toArray() + } + + @Override + boolean test(ItemStack itemStack) { + return ItemMeta.compare(itemStack, stone) + } +} + +// Without Strict: Shaped +crafting.shapedBuilder() + .name('shaped-no-strict') + .output(item('minecraft:dirt')) + .matrix([[new TestStone()]]) + .register() + +// With Strict: Shaped +crafting.shapedBuilder() + .name('shaped-strict') + .output(item('minecraft:dirt')) + .matrix([[new TestStone()]]) + .strictJEIHandling() + .register() + +// Without Strict: Shapeless +crafting.shapelessBuilder() + .name('shapeless-no-strict') + .output(item('minecraft:dirt')) + .input(new TestStone()) + .register() + +// With Strict: Shapeless +crafting.shapelessBuilder() + .name('shapeless-strict') + .output(item('minecraft:dirt')) + .input(new TestStone()) + .strictJEIHandling() + .register() + diff --git a/src/main/groovy-tests/dmeSimChamberTests.groovy b/src/main/groovy-tests/dmeSimChamberTests.groovy index cbe747b0..22d41941 100644 --- a/src/main/groovy-tests/dmeSimChamberTests.groovy +++ b/src/main/groovy-tests/dmeSimChamberTests.groovy @@ -1,10 +1,12 @@ +//MODS_LOADED: deepmoblearning + import com.nomiceu.nomilabs.LabsValues import mustapelto.deepmoblearning.common.metadata.MetadataDataModel import mustapelto.deepmoblearning.common.metadata.MetadataManager import net.minecraftforge.fml.common.Loader // Demonstration of Dynamically Generated DME Sim Chamber Recipes. (Goes in Post Init) -// DOES NOT WORK IF DME IS NOT INCLUDED IN LOAD! +// WILL NOT LOAD IF DME IS NOT INCLUDED! def models = MetadataManager.dataModelMetadataList for (var model : models) { diff --git a/src/main/groovy-tests/miscTests.groovy b/src/main/groovy-tests/ncCoolerTests.groovy similarity index 88% rename from src/main/groovy-tests/miscTests.groovy rename to src/main/groovy-tests/ncCoolerTests.groovy index 315ddbd7..30f0f191 100644 --- a/src/main/groovy-tests/miscTests.groovy +++ b/src/main/groovy-tests/ncCoolerTests.groovy @@ -1,7 +1,11 @@ +//MODS_LOADED: nuclearcraft + import com.nomiceu.nomilabs.groovy.NCActiveCoolerHelper import static nc.enumm.MetaEnums.CoolerType.* +// Does not load if NuclearCraft is not loaded! + /** * Change a NuclearCraft Active Cooler Recipe.
* Inputs:
diff --git a/src/main/java/com/nomiceu/nomilabs/groovy/SimpleIIngredient.java b/src/main/java/com/nomiceu/nomilabs/groovy/SimpleIIngredient.java new file mode 100644 index 00000000..418475e3 --- /dev/null +++ b/src/main/java/com/nomiceu/nomilabs/groovy/SimpleIIngredient.java @@ -0,0 +1,42 @@ +package com.nomiceu.nomilabs.groovy; + +import net.minecraft.item.crafting.Ingredient; + +import org.jetbrains.annotations.Nullable; + +import com.cleanroommc.groovyscript.api.IIngredient; + +/** + * A template for a simple implementation of IIngredient, with stack size 1, no marks, and no copying. + *

+ * Also sets `toMcIngredient` to return an ingredient of the matching stacks. + */ +public abstract class SimpleIIngredient implements IIngredient { + + @Override + public Ingredient toMcIngredient() { + return Ingredient.fromStacks(getMatchingStacks()); + } + + @Override + public int getAmount() { + return 1; + } + + @Override + public void setAmount(int amount) {} + + @Override + public IIngredient exactCopy() { + return this; + } + + @Nullable + @Override + public String getMark() { + return null; + } + + @Override + public void setMark(String mark) {} +} diff --git a/src/main/java/com/nomiceu/nomilabs/groovy/mixinhelper/StrictableItemRecipeWrappers.java b/src/main/java/com/nomiceu/nomilabs/groovy/mixinhelper/StrictableItemRecipeWrappers.java new file mode 100644 index 00000000..9daaee7b --- /dev/null +++ b/src/main/java/com/nomiceu/nomilabs/groovy/mixinhelper/StrictableItemRecipeWrappers.java @@ -0,0 +1,80 @@ +package com.nomiceu.nomilabs.groovy.mixinhelper; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import net.minecraft.item.ItemStack; +import net.minecraft.item.crafting.IRecipe; +import net.minecraftforge.common.crafting.IShapedRecipe; + +import org.jetbrains.annotations.NotNull; + +import com.cleanroommc.groovyscript.compat.mods.jei.ShapedRecipeWrapper; + +import mezz.jei.api.IJeiHelpers; +import mezz.jei.api.ingredients.IIngredients; +import mezz.jei.api.ingredients.VanillaTypes; +import mezz.jei.api.recipe.IStackHelper; +import mezz.jei.plugins.vanilla.crafting.ShapelessRecipeWrapper; +import mezz.jei.recipes.BrokenCraftingRecipeException; +import mezz.jei.util.ErrorUtil; + +/** + * Recipe wrappers that can return the matching stacks exactly for JEI, + * ignoring duplicates according to JEI and wildcard itemstacks. + */ +public class StrictableItemRecipeWrappers { + + public static void getIngredientsImpl(@NotNull IIngredients ingredients, IRecipe recipe, IJeiHelpers jeiHelpers) { + ItemStack recipeOutput = recipe.getRecipeOutput(); + List> inputLists; + + try { + if (recipe instanceof StrictableRecipe strict && strict.labs$getIsStrict()) { + inputLists = new ArrayList<>(); + for (var input : recipe.getIngredients()) { + inputLists.add(new ArrayList<>(Arrays.asList(input.getMatchingStacks()))); + } + } else { + IStackHelper stackHelper = jeiHelpers.getStackHelper(); + inputLists = stackHelper.expandRecipeItemStackInputs(recipe.getIngredients()); + } + ingredients.setInputLists(VanillaTypes.ITEM, inputLists); + ingredients.setOutput(VanillaTypes.ITEM, recipeOutput); + } catch (RuntimeException e) { + String info = ErrorUtil.getInfoFromBrokenCraftingRecipe(recipe, recipe.getIngredients(), recipeOutput); + throw new BrokenCraftingRecipeException(info, e); + } + } + + public static class Shapeless extends ShapelessRecipeWrapper { + + protected final IJeiHelpers jeiHelpers; + + public Shapeless(IJeiHelpers jeiHelpers, IRecipe recipe) { + super(jeiHelpers, recipe); + this.jeiHelpers = jeiHelpers; + } + + @Override + public void getIngredients(@NotNull IIngredients ingredients) { + StrictableItemRecipeWrappers.getIngredientsImpl(ingredients, recipe, jeiHelpers); + } + } + + public static class Shaped extends ShapedRecipeWrapper { + + protected final IJeiHelpers jeiHelpers; + + public Shaped(IJeiHelpers jeiHelpers, IShapedRecipe recipe) { + super(jeiHelpers, recipe); + this.jeiHelpers = jeiHelpers; + } + + @Override + public void getIngredients(@NotNull IIngredients ingredients) { + StrictableItemRecipeWrappers.getIngredientsImpl(ingredients, recipe, jeiHelpers); + } + } +} diff --git a/src/main/java/com/nomiceu/nomilabs/groovy/mixinhelper/StrictableRecipe.java b/src/main/java/com/nomiceu/nomilabs/groovy/mixinhelper/StrictableRecipe.java new file mode 100644 index 00000000..ad106604 --- /dev/null +++ b/src/main/java/com/nomiceu/nomilabs/groovy/mixinhelper/StrictableRecipe.java @@ -0,0 +1,12 @@ +package com.nomiceu.nomilabs.groovy.mixinhelper; + +/** + * A recipe that can return the matching stacks exactly for JEI, + * ignoring duplicates according to JEI and wildcard itemstacks. + */ +public interface StrictableRecipe { + + void labs$setStrict(); + + boolean labs$getIsStrict(); +} diff --git a/src/main/java/com/nomiceu/nomilabs/mixin/groovyscript/CraftingRecipeMixin.java b/src/main/java/com/nomiceu/nomilabs/mixin/groovyscript/CraftingRecipeMixin.java new file mode 100644 index 00000000..0ba09e1e --- /dev/null +++ b/src/main/java/com/nomiceu/nomilabs/mixin/groovyscript/CraftingRecipeMixin.java @@ -0,0 +1,30 @@ +package com.nomiceu.nomilabs.mixin.groovyscript; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; + +import com.cleanroommc.groovyscript.compat.vanilla.CraftingRecipe; +import com.nomiceu.nomilabs.groovy.mixinhelper.StrictableRecipe; + +/** + * Allows for recipes to be 'strict'. This means, in JEI, the list of 'matching stacks' will be displayed + * exactly as set, instead of expanding wildcards and removing duplicates. + */ +@Mixin(value = CraftingRecipe.class, remap = false) +public class CraftingRecipeMixin implements StrictableRecipe { + + @Unique + private boolean labs$isStrict = false; + + @Override + @Unique + public boolean labs$getIsStrict() { + return labs$isStrict; + } + + @Override + @Unique + public void labs$setStrict() { + labs$isStrict = true; + } +} diff --git a/src/main/java/com/nomiceu/nomilabs/mixin/groovyscript/JEIPluginMixin.java b/src/main/java/com/nomiceu/nomilabs/mixin/groovyscript/JEIPluginMixin.java new file mode 100644 index 00000000..3ef4752d --- /dev/null +++ b/src/main/java/com/nomiceu/nomilabs/mixin/groovyscript/JEIPluginMixin.java @@ -0,0 +1,36 @@ +package com.nomiceu.nomilabs.mixin.groovyscript; + +import net.minecraft.item.crafting.IRecipe; +import net.minecraftforge.common.crafting.IShapedRecipe; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +import com.cleanroommc.groovyscript.compat.mods.jei.JeiPlugin; +import com.cleanroommc.groovyscript.compat.mods.jei.ShapedRecipeWrapper; +import com.nomiceu.nomilabs.groovy.mixinhelper.StrictableItemRecipeWrappers; + +import mezz.jei.api.IJeiHelpers; +import mezz.jei.plugins.vanilla.crafting.ShapelessRecipeWrapper; + +/** + * Registers recipes with the new 'strictable' wrappers. + */ +@Mixin(value = JeiPlugin.class, remap = false) +public class JEIPluginMixin { + + @Redirect(method = "lambda$register$0", + at = @At(value = "NEW", + target = "(Lmezz/jei/api/IJeiHelpers;Lnet/minecraftforge/common/crafting/IShapedRecipe;)Lcom/cleanroommc/groovyscript/compat/mods/jei/ShapedRecipeWrapper;")) + private static ShapedRecipeWrapper returnNewHandlerShaped(IJeiHelpers jeiHelpers, IShapedRecipe recipe) { + return new StrictableItemRecipeWrappers.Shaped(jeiHelpers, recipe); + } + + @Redirect(method = "lambda$register$1", + at = @At(value = "NEW", + target = "(Lmezz/jei/api/IJeiHelpers;Lnet/minecraft/item/crafting/IRecipe;)Lmezz/jei/plugins/vanilla/crafting/ShapelessRecipeWrapper;")) + private static ShapelessRecipeWrapper returnNewHandlerShapeless(IJeiHelpers jeiHelpers, IRecipe recipe) { + return new StrictableItemRecipeWrappers.Shapeless(jeiHelpers, recipe); + } +} diff --git a/src/main/java/com/nomiceu/nomilabs/mixin/groovyscript/ShapedRecipeBuilderMixin.java b/src/main/java/com/nomiceu/nomilabs/mixin/groovyscript/ShapedRecipeBuilderMixin.java index ab2d22fc..0bb7699e 100644 --- a/src/main/java/com/nomiceu/nomilabs/mixin/groovyscript/ShapedRecipeBuilderMixin.java +++ b/src/main/java/com/nomiceu/nomilabs/mixin/groovyscript/ShapedRecipeBuilderMixin.java @@ -7,7 +7,9 @@ import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import com.cleanroommc.groovyscript.api.GroovyLog; import com.cleanroommc.groovyscript.api.IIngredient; @@ -15,6 +17,7 @@ import com.cleanroommc.groovyscript.registry.AbstractCraftingRecipeBuilder; import com.nomiceu.nomilabs.groovy.mixinhelper.ShapedRecipeClassFunction; import com.nomiceu.nomilabs.groovy.mixinhelper.ShapedRecipeClassFunctionSimplified; +import com.nomiceu.nomilabs.groovy.mixinhelper.StrictableRecipe; import it.unimi.dsi.fastutil.chars.Char2ObjectOpenHashMap; @@ -23,7 +26,10 @@ public abstract class ShapedRecipeBuilderMixin extends AbstractCraftingRecipeBuilder.AbstractShaped { @Unique - private ShapedRecipeClassFunction recipeClassFunction = null; + private ShapedRecipeClassFunction labs$recipeClassFunction = null; + + @Unique + private boolean labs$isStrict = false; /** * Default Ignored Constructor @@ -32,17 +38,35 @@ public ShapedRecipeBuilderMixin(int width, int height) { super(width, height); } + /** + * Makes recipes 'strict'. This means, in JEI, the list of 'matching stacks' will be displayed + * exactly as set, instead of expanding wildcards and removing duplicates. + */ + @Unique + public CraftingRecipeBuilder.Shaped strictJEIHandling() { + labs$isStrict = true; + return (CraftingRecipeBuilder.Shaped) (Object) this; + } + + @Inject(method = "register()Lnet/minecraft/item/crafting/IRecipe;", at = @At("RETURN")) + private void setStrict(CallbackInfoReturnable cir) { + var val = cir.getReturnValue(); + if (!(val instanceof StrictableRecipe strict)) return; + + if (labs$isStrict) strict.labs$setStrict(); + } + @Unique public CraftingRecipeBuilder.Shaped recipeClassFunction(ShapedRecipeClassFunction recipeClassFunction) { - this.recipeClassFunction = recipeClassFunction; + this.labs$recipeClassFunction = recipeClassFunction; return (CraftingRecipeBuilder.Shaped) (Object) this; } @Unique public CraftingRecipeBuilder.Shaped recipeClassFunction(ShapedRecipeClassFunctionSimplified recipeClassFunction) { - this.recipeClassFunction = (output1, width1, height1, ingredients, _mirrored, _recipeFunction, - _recipeAction) -> recipeClassFunction.createRecipe(output1, width1, height1, - ingredients); + this.labs$recipeClassFunction = (output1, width1, height1, ingredients, _mirrored, _recipeFunction, + _recipeAction) -> recipeClassFunction.createRecipe(output1, width1, height1, + ingredients); return (CraftingRecipeBuilder.Shaped) (Object) this; } @@ -54,10 +78,10 @@ public Object registerWithClassFunction1(CraftingRecipeBuilder.Shaped instance, List list, String[] strings, Char2ObjectOpenHashMap char2ObjectOpenHashMap, IRecipeCreator recipeCreator) { - if (recipeClassFunction == null) + if (labs$recipeClassFunction == null) return validateShape(msg, list, strings, char2ObjectOpenHashMap, recipeCreator); return validateShape(msg, list, strings, char2ObjectOpenHashMap, - (width1, height1, ingredients) -> recipeClassFunction + (width1, height1, ingredients) -> labs$recipeClassFunction .createRecipe(output, width1, height1, ingredients, mirrored, recipeFunction, recipeAction)); } @@ -67,8 +91,8 @@ public Object registerWithClassFunction1(CraftingRecipeBuilder.Shaped instance, require = 1) public Object registerWithClassFunction2(CraftingRecipeBuilder.Shaped instance, GroovyLog.Msg msg, List> list, IRecipeCreator recipeCreator) { - if (recipeClassFunction == null) return validateShape(msg, list, recipeCreator); - return validateShape(msg, list, (width1, height1, ingredients) -> recipeClassFunction + if (labs$recipeClassFunction == null) return validateShape(msg, list, recipeCreator); + return validateShape(msg, list, (width1, height1, ingredients) -> labs$recipeClassFunction .createRecipe(output, width1, height1, ingredients, mirrored, recipeFunction, recipeAction)); } } diff --git a/src/main/java/com/nomiceu/nomilabs/mixin/groovyscript/ShapelessRecipeBuilderMixin.java b/src/main/java/com/nomiceu/nomilabs/mixin/groovyscript/ShapelessRecipeBuilderMixin.java new file mode 100644 index 00000000..d158afa2 --- /dev/null +++ b/src/main/java/com/nomiceu/nomilabs/mixin/groovyscript/ShapelessRecipeBuilderMixin.java @@ -0,0 +1,42 @@ +package com.nomiceu.nomilabs.mixin.groovyscript; + +import net.minecraft.item.crafting.IRecipe; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import com.cleanroommc.groovyscript.compat.vanilla.CraftingRecipeBuilder; +import com.nomiceu.nomilabs.groovy.mixinhelper.StrictableRecipe; + +/** + * Allows for recipes to be 'strict'. This means, in JEI, the list of 'matching stacks' will be displayed + * exactly as set, instead of expanding wildcards and removing duplicates. + */ +@Mixin(value = CraftingRecipeBuilder.Shapeless.class, remap = false) +@SuppressWarnings("unused") +public class ShapelessRecipeBuilderMixin { + + @Unique + private boolean labs$isStrict = false; + + /** + * Makes recipes 'strict'. This means, in JEI, the list of 'matching stacks' will be displayed + * exactly as set, instead of expanding wildcards and removing duplicates. + */ + @Unique + public CraftingRecipeBuilder.Shapeless strictJEIHandling() { + labs$isStrict = true; + return (CraftingRecipeBuilder.Shapeless) (Object) this; + } + + @Inject(method = "register()Lnet/minecraft/item/crafting/IRecipe;", at = @At("RETURN")) + private void setStrict(CallbackInfoReturnable cir) { + var val = cir.getReturnValue(); + if (!(val instanceof StrictableRecipe strict)) return; + + if (labs$isStrict) strict.labs$setStrict(); + } +} diff --git a/src/main/resources/mixins.nomilabs.groovyscript.json b/src/main/resources/mixins.nomilabs.groovyscript.json index 65822c0d..3d8a8388 100644 --- a/src/main/resources/mixins.nomilabs.groovyscript.json +++ b/src/main/resources/mixins.nomilabs.groovyscript.json @@ -5,9 +5,12 @@ "minVersion": "0.8", "compatibilityLevel": "JAVA_8", "mixins": [ + "CraftingRecipeMixin", "InfoParserOreDictMixin", "IngredientMixin", - "ShapedRecipeBuilderMixin" + "JEIPluginMixin", + "ShapedRecipeBuilderMixin", + "ShapelessRecipeBuilderMixin" ], "client": [], "server": []