diff --git a/.gitignore b/.gitignore index 12f8644..6cade8e 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,6 @@ run # Files from Forge MDK forge*changelog.txt + +# Allow +!run/scripts/test.zs diff --git a/docsOut/docs.json b/docsOut/docs.json index 68e8ee4..aa27831 100644 --- a/docsOut/docs.json +++ b/docsOut/docs.json @@ -1 +1 @@ -{"nav":{"Mods":{"GenericAddon":{"InfusionRecipeManager":"mods/GenericAddon/InfusionRecipeManager.md","MyStaticIntegrationClass":"mods/GenericAddon/MyStaticIntegrationClass.md"}}}} \ No newline at end of file +{"nav":{"Mods":{"GenericAddon":{"InfusionRecipe":"mods/GenericAddon/InfusionRecipe.md","InfusionRecipeManager":"mods/GenericAddon/InfusionRecipeManager.md","MyStaticIntegrationClass":"mods/GenericAddon/MyStaticIntegrationClass.md"}}}} \ No newline at end of file diff --git a/docsOut/docs/mods/GenericAddon/InfusionRecipe.json b/docsOut/docs/mods/GenericAddon/InfusionRecipe.json new file mode 100644 index 0000000..562f70a --- /dev/null +++ b/docsOut/docs/mods/GenericAddon/InfusionRecipe.json @@ -0,0 +1 @@ +{"path":"mods/GenericAddon/InfusionRecipe.md","zenCodeName":"mods.genericaddon.InfusionRecipe"} diff --git a/docsOut/docs/mods/GenericAddon/InfusionRecipe.md b/docsOut/docs/mods/GenericAddon/InfusionRecipe.md new file mode 100644 index 0000000..e993f0a --- /dev/null +++ b/docsOut/docs/mods/GenericAddon/InfusionRecipe.md @@ -0,0 +1,29 @@ +# InfusionRecipe + +A class exposing your Recipe to the Zen Code engine. + + This is useful to scripters as having the explicit type allows them to access specific members of the class, rather + than going through Recipe methods. + + You're expected to also document the [IRecipeComponent](/vanilla/api/recipe/IRecipeComponent)<T>s your + recipe can break into. For more information, see the appropriate handler class: + **invalid** + + ## Recipe Components + + - `<recipecomponent:crafttweaker:input/ingredients>`, of type [IIngredient](/vanilla/api/ingredient/IIngredient) + - `<recipecomponent:crafttweaker:output/items>`, of type [IItemStack](/vanilla/api/item/IItemStack) + +## Importing the class + +It might be required for you to import the package if you encounter any issues (like casting an Array), so better be safe than sorry and add the import at the very top of the file. +```zenscript +import mods.genericaddon.InfusionRecipe; +``` + + +## Implemented Interfaces +InfusionRecipe implements the following interfaces. That means all methods defined in these interfaces are also available in InfusionRecipe + +- [Recipe](/vanilla/api/recipe/type/Recipe)<[Container](/vanilla/api/world/Container)> + diff --git a/docsOut/docs/mods/genericaddon/InfusionRecipeManager.json b/docsOut/docs/mods/genericaddon/InfusionRecipeManager.json index 8d22e6e..bbc75df 100644 --- a/docsOut/docs/mods/genericaddon/InfusionRecipeManager.json +++ b/docsOut/docs/mods/genericaddon/InfusionRecipeManager.json @@ -1 +1 @@ -{"path":"mods/GenericAddon/InfusionRecipeManager.md","zenCodeName":"mods.genericaddon.InfusionRecipeManager","searchTerms":["getRecipeByName","addJsonRecipe","allRecipes","removeByModid","remove","recipeMap","addRecipe","removeAll","removeByName","getRecipeMap","getAllRecipes","removeByInput","crafttweaker.api.recipe.IRecipeManager","getRecipesByOutput","removeByRegex"]} +{"path":"mods/GenericAddon/InfusionRecipeManager.md","zenCodeName":"mods.genericaddon.InfusionRecipeManager","searchTerms":["getRecipeByName","addJsonRecipe","allRecipes","removeByModid","remove","recipeMap","addRecipe","removeAll","removeByName","getRecipeMap","getAllRecipes","removeByInput","getRecipesByOutput","removeByRegex"]} diff --git a/docsOut/docs/mods/genericaddon/InfusionRecipeManager.md b/docsOut/docs/mods/genericaddon/InfusionRecipeManager.md index b6a380e..f2c1dcd 100644 --- a/docsOut/docs/mods/genericaddon/InfusionRecipeManager.md +++ b/docsOut/docs/mods/genericaddon/InfusionRecipeManager.md @@ -29,7 +29,7 @@ import mods.genericaddon.InfusionRecipeManager; ## Implemented Interfaces InfusionRecipeManager implements the following interfaces. That means all methods defined in these interfaces are also available in InfusionRecipeManager -- [IRecipeManager](/vanilla/api/recipe/manager/IRecipeManager) +- [IRecipeManager](/vanilla/api/recipe/manager/IRecipeManager)<[InfusionRecipe](/mods/GenericAddon/InfusionRecipe)> ## Methods @@ -56,16 +56,16 @@ Any added methods should be instance methods, if the AP was added to the compiler's classpath. ```zenscript -// InfusionRecipeManager.addRecipe(name as string, input as IIngredient, output as IItemStack) +// InfusionRecipeManager.addRecipe(name as string, output as IItemStack, input as IIngredient) -.addRecipe("my_recipe", , * 16); +.addRecipe("my_recipe", * 16, ); ``` | Parameter | Type | Description | |-----------|----------------------------------------------------|-------------------------------------------------------------| | name | string | The recipe name, only the resource path (without namespace) | -| input | [IIngredient](/vanilla/api/ingredient/IIngredient) | The recipe's input | | output | [IItemStack](/vanilla/api/item/IItemStack) | The recipe result | +| input | [IIngredient](/vanilla/api/ingredient/IIngredient) | The recipe's input | ::: diff --git a/run/scripts/test.zs b/run/scripts/test.zs new file mode 100644 index 0000000..10d5e71 --- /dev/null +++ b/run/scripts/test.zs @@ -0,0 +1,42 @@ +import crafttweaker.api.ingredient.IIngredient; +import crafttweaker.api.recipe.replacement.Replacer; +import mods.genericaddon.MyStaticIntegrationClass; + +MyStaticIntegrationClass.doPrint(); + +.addRecipe("script_test", , ); + +/* +Before the Replacer call is executed. Feel free to comment the replacer out to check yourself. +[INFO][CraftTweaker-Commands]: Recipe type: '' + .addRecipe("genericaddon:recipe_a", , ); + .addRecipe("genericaddon:recipe_b", , ); + .addRecipe("genericaddon:recipe_c", , ); + .addRecipe("crafttweaker:script_test", , ); +*/ + +Replacer.create() +.replace(, , ) +.execute(); + +/* +After it is executed: +[INFO][CraftTweaker-Commands]: Recipe type: '' + .addRecipe("crafttweaker:autogenerated/replacer/genericaddon.recipe_b.1", , ); + .addRecipe("genericaddon:recipe_a", , ); + .addRecipe("genericaddon:recipe_c", , ); + .addRecipe("crafttweaker:script_test", , ); +*/ + +.removeByName("genericaddon:recipe_a"); + +/* +Final state: + +[INFO][CraftTweaker-Commands]: Recipe type: '' + .addRecipe("crafttweaker:autogenerated/replacer/genericaddon.recipe_b.1", , ); + .addRecipe("genericaddon:recipe_a", , ); + .addRecipe("genericaddon:recipe_c", , ); + .addRecipe("crafttweaker:script_test", , ); + +*/ diff --git a/src/main/java/com/blamejared/genericaddon/SomeGenericAddon.java b/src/main/java/com/blamejared/genericaddon/SomeGenericAddon.java index 0a40e00..1787c69 100644 --- a/src/main/java/com/blamejared/genericaddon/SomeGenericAddon.java +++ b/src/main/java/com/blamejared/genericaddon/SomeGenericAddon.java @@ -30,5 +30,6 @@ public class SomeGenericAddon { public SomeGenericAddon() { IEventBus modBus = FMLJavaModLoadingContext.get().getModEventBus(); SERIALIZER_REGISTRY.register(modBus); + RECIPE_REGISTRY.register(modBus); } } diff --git a/src/main/java/com/blamejared/genericaddon/compat/crafttweaker/MyStaticIntegrationClass.java b/src/main/java/com/blamejared/genericaddon/compat/crafttweaker/MyStaticIntegrationClass.java index 854a96b..7dc4679 100644 --- a/src/main/java/com/blamejared/genericaddon/compat/crafttweaker/MyStaticIntegrationClass.java +++ b/src/main/java/com/blamejared/genericaddon/compat/crafttweaker/MyStaticIntegrationClass.java @@ -1,10 +1,10 @@ package com.blamejared.genericaddon.compat.crafttweaker; -import com.blamejared.crafttweaker.api.*; +import com.blamejared.crafttweaker.api.CraftTweakerAPI; import com.blamejared.crafttweaker.api.annotation.ZenRegister; import com.blamejared.crafttweaker.api.ingredient.IIngredient; -import com.blamejared.crafttweaker_annotations.annotations.*; -import org.openzen.zencode.java.*; +import com.blamejared.crafttweaker_annotations.annotations.Document; +import org.openzen.zencode.java.ZenCodeType; /** * A class with static members that allow us to do stuff! diff --git a/src/main/java/com/blamejared/genericaddon/compat/crafttweaker/handlers/InfusionRecipeHandler.java b/src/main/java/com/blamejared/genericaddon/compat/crafttweaker/handlers/InfusionRecipeHandler.java new file mode 100644 index 0000000..476803a --- /dev/null +++ b/src/main/java/com/blamejared/genericaddon/compat/crafttweaker/handlers/InfusionRecipeHandler.java @@ -0,0 +1,161 @@ +package com.blamejared.genericaddon.compat.crafttweaker.handlers; + +import com.blamejared.crafttweaker.api.ingredient.IIngredient; +import com.blamejared.crafttweaker.api.item.IItemStack; +import com.blamejared.crafttweaker.api.recipe.component.BuiltinRecipeComponents; +import com.blamejared.crafttweaker.api.recipe.component.DecomposedRecipeBuilder; +import com.blamejared.crafttweaker.api.recipe.component.IDecomposedRecipe; +import com.blamejared.crafttweaker.api.recipe.component.IRecipeComponent; +import com.blamejared.crafttweaker.api.recipe.handler.IRecipeHandler; +import com.blamejared.crafttweaker.api.recipe.manager.base.IRecipeManager; +import com.blamejared.crafttweaker.api.util.IngredientUtil; +import com.blamejared.crafttweaker.api.util.ItemStackUtil; +import com.blamejared.crafttweaker.api.util.StringUtil; +import com.blamejared.genericaddon.recipes.InfusionRecipe; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.item.crafting.Recipe; +import net.minecraft.world.item.crafting.RecipeType; +import net.minecraftforge.server.ServerLifecycleHooks; + +import java.util.Optional; + +/** + * An {@link IRecipeHandler} is a class that adds compatibility between a Recipe and CraftTweaker features. + * + * As of now, these include: conflict checking, dumping recipes to log, and replacing. + * + */ +@IRecipeHandler.For(InfusionRecipe.class) +public class InfusionRecipeHandler implements IRecipeHandler { + + + /** + * Creates a String representation of a valid {@code addRecipe} (or alternative) call for the given subclass of + * {@link Recipe}. + * + *

Recipe dumps are triggered by the {@code /ct recipes} or {@code /ct recipes hand} commands.

+ * + *

All newlines added to either the start or the end of the string will be automatically trimmed.

+ * + * @param manager The recipe manager responsible for this kind of recipes. + * @param recipe The recipe that is currently being dumped. + * @return A String representing a {@code addRecipe} (or similar) call. + * @since 9.0.0 + */ + @Override + public String dumpToCommandString(IRecipeManager manager, InfusionRecipe recipe){ + + //Generates + // .addRecipe("genericaddon:recipe_a", , ); + // which is the exact method call used to add the recipe + return String.format("%s.addRecipe(%s, %s, %s);", + manager.getCommandString(), + StringUtil.quoteAndEscape(recipe.getId()), + ItemStackUtil.getCommandString(recipe.getResultItem(ServerLifecycleHooks.getCurrentServer().registryAccess())), + IIngredient.fromIngredient(recipe.getInput()).getCommandString() + ); + } + + /** + * Checks if the two recipes conflict with each other. + * + *

In this case, a conflict is defined as the two recipes being made in the exact same way (e.g. with the same + * shape and the same ingredients if the two recipes are shaped crafting table ones).

+ * + *

Conflicts are also considered symmetrical in this implementation, which means that if {@code firstRecipe} + * conflicts with {@code secondRecipe}, the opposite is also true.

+ * + * @param manager The recipe manager responsible for this kind of recipes. + * @param firstRecipe The recipe which should be checked for conflict. + * @param secondRecipe The other recipe which {@code firstRecipe} should be checked against. The recipe may or may + * not be of the same type of {@code firstRecipe}. See the API note section for more details. + * @return Whether the {@code firstRecipe} conflicts with {@code secondRecipe} or not. + * @apiNote The reason for which {@code secondRecipe} is specified as simply {@link Recipe} instead of as the + * generic parameter {@code T} is to allow more flexibility in the conflict checking. In fact, this choice allows + * for checking to also occur between different types of recipes (e.g. shaped vs shapeless crafting table recipes), + * allowing for a broader range of checking. Nevertheless, the two recipes are ensured to be of the + * same {@link RecipeType recipe type} (i.e. + * {@code firstRecipe.getType() == secondRecipe.getType()}). + * @since 9.0.0 + */ + @Override + public > boolean doesConflict(IRecipeManager manager, InfusionRecipe firstRecipe, U secondRecipe){ + //Since we know both recipes must be of InfusionRecipe, we check it like this. + //In the event you have multiple types of recipes, check other CrT examples. + return IngredientUtil.canConflict(firstRecipe.getInput(), secondRecipe.getIngredients().get(0)); + } + + /** + * Decomposes a recipe from its complete form into an {@link IDecomposedRecipe}. + * + *

The decomposition needs to be complete, meaning that any meaningful part of the recipe should be present in + * the returned decomposed recipe. The only exception is the name, as decomposed recipes only track + * {@link IRecipeComponent}s, and names aren't one.

+ * + *

It is allowed for an implementation to specify that the given recipe cannot be properly decomposed. Examples + * of this occurrence might be recipes whose behavior is completely determined by code, such as map cloning in + * vanilla. In this context, it is allowed to return {@link Optional#empty()}.

+ * + *

It is mandatory for a recipe handler to produce a decomposed recipe that can then be converted back into its + * complete form in {@link #recompose(IRecipeManager, ResourceLocation, IDecomposedRecipe)}. In other words, if + * the return value of this method isn't empty, then + * {@code recompose(manager, name, decompose(manager, recipe).get())} must not return an empty optional.

+ * + * @param manager The recipe manager responsible for this kind of recipes. + * @param recipe The recipe that should be decomposed. + * @return An {@link Optional} wrapping {@linkplain IDecomposedRecipe decomposed recipe}, or an empty one if need be + * as specified above. + * @since 10.0.0 + */ + @Override + public Optional decompose(IRecipeManager manager, InfusionRecipe recipe){ + DecomposedRecipeBuilder decomposedRecipe = IDecomposedRecipe.builder(); + decomposedRecipe.with(BuiltinRecipeComponents.Input.INGREDIENTS, IIngredient.fromIngredient(recipe.getInput())); + decomposedRecipe.with(BuiltinRecipeComponents.Output.ITEMS, + IItemStack.of(recipe.getResultItem(ServerLifecycleHooks.getCurrentServer().registryAccess()))); + //Other components are available in BuiltinRecipeComponents. + //Should you need to create a new one see ICraftTweakerPlugin#registerRecipeComponents + //Don't forget to Document those in the Recipe Class javadocs. + return Optional.of(decomposedRecipe.build()); + } + + /** + * Reconstructs a complete recipe from its {@linkplain IDecomposedRecipe decomposed form}. + * + *

The recomposition should be as complete as possible, making sure that all + * {@link IRecipeComponent}s that are necessary to properly rebuild + * the recipe are present in the given {@link IDecomposedRecipe}. If the recipe presents unknown components, i.e. + * components that this handler doesn't know how to convert, the handler is allowed to throw an exception as + * detailed in the following paragraphs.

+ * + *

It is allowed for an implementation to return {@link Optional#empty()} in case the recomposition fails for + * any reason, or if no decomposed recipe can be used to rebuild a recipe in its complete form, e.g. for map cloning + * in vanilla.

+ * + *

It is mandatory for a recipe handler that knows how to decompose a recipe to also know how to recompose it. In + * other words, if {@link #decompose(IRecipeManager, Recipe)} returns a non-empty {@code Optional}, then + * {@code recompose(manager, name, decompose(manager, recipe).get())} must not return an empty optional nor throw + * an exception.

+ * + * @param manager The recipe manager responsible for this kind of recipes. + * @param name The name to give to the recomposed recipe once it has been built. It is mandatory that + * {@link T#getId()} and this parameter represent the same name. + * @param recipe The {@link IDecomposedRecipe} that should be recomposed back into a complete form. + * @return An {@link Optional} wrapping the complete form of the recipe, or an empty one if need be as specified + * above. + * @throws IllegalArgumentException If any of the required recipe components are not present in the recipe, or they + * have invalid or meaningless values (e.g. an empty output). Optionally, if any + * unknown component is present in the decomposed recipe. + * @since 10.0.0 + */ + @Override + public Optional recompose(IRecipeManager manager, ResourceLocation name, IDecomposedRecipe recipe){ + final IIngredient input = recipe.getOrThrowSingle(BuiltinRecipeComponents.Input.INGREDIENTS); + final IItemStack output = recipe.getOrThrowSingle(BuiltinRecipeComponents.Output.ITEMS); + + if (input.isEmpty()) throw new IllegalArgumentException("Invalid input: empty ingredient"); + if (output.isEmpty()) throw new IllegalArgumentException("Invalid output: empty stack"); + return Optional.of(new InfusionRecipe(name, input.asVanillaIngredient(), output.getInternal())); + } +} diff --git a/src/main/java/com/blamejared/genericaddon/compat/crafttweaker/managers/InfusionRecipeManager.java b/src/main/java/com/blamejared/genericaddon/compat/crafttweaker/managers/InfusionRecipeManager.java index 727e86b..6700ed7 100644 --- a/src/main/java/com/blamejared/genericaddon/compat/crafttweaker/managers/InfusionRecipeManager.java +++ b/src/main/java/com/blamejared/genericaddon/compat/crafttweaker/managers/InfusionRecipeManager.java @@ -1,7 +1,6 @@ package com.blamejared.genericaddon.compat.crafttweaker.managers; -import com.blamejared.crafttweaker.CraftTweakerCommon; import com.blamejared.crafttweaker.api.CraftTweakerAPI; import com.blamejared.crafttweaker.api.CraftTweakerConstants; import com.blamejared.crafttweaker.api.action.recipe.ActionAddRecipe; @@ -16,7 +15,7 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.crafting.Ingredient; import net.minecraft.world.item.crafting.RecipeType; -import org.openzen.zencode.java.*; +import org.openzen.zencode.java.ZenCodeType; /** * By default, CraftTweaker creates a {@link com.blamejared.crafttweaker.api.recipe.manager.RecipeManagerWrapper} @@ -79,14 +78,14 @@ public InfusionRecipeManager() { * if the AP was added to the compiler's classpath. * * @param name The recipe name, only the resource path (without namespace) - * @param input The recipe's input * @param output The recipe result + * @param input The recipe's input * @docParam name "my_recipe" - * @docParam input * @docParam output * 16 + * @docParam input */ @ZenCodeType.Method - public void addRecipe(String name, IIngredient input, IItemStack output) { + public void addRecipe(String name, IItemStack output, IIngredient input) { //Your recipes should be using CraftTweaker's namespace, so that anyone reading this can pick up that this recipe //was added by a CraftTweaker scripts. final ResourceLocation id = CraftTweakerConstants.rl(fixRecipeName(name)); diff --git a/src/main/java/com/blamejared/genericaddon/compat/crafttweaker/natives/ExposeInfusionRecipe.java b/src/main/java/com/blamejared/genericaddon/compat/crafttweaker/natives/ExposeInfusionRecipe.java new file mode 100644 index 0000000..dfaec76 --- /dev/null +++ b/src/main/java/com/blamejared/genericaddon/compat/crafttweaker/natives/ExposeInfusionRecipe.java @@ -0,0 +1,28 @@ +package com.blamejared.genericaddon.compat.crafttweaker.natives; + + +import com.blamejared.crafttweaker_annotations.annotations.Document; +import com.blamejared.crafttweaker_annotations.annotations.NativeTypeRegistration; +import com.blamejared.genericaddon.recipes.InfusionRecipe; + +/** + *

A class exposing your Recipe to the Zen Code engine.

+ * + *

This is useful to scripters as having the explicit type allows them to access specific members of the class, rather + * than going through Recipe methods.

+ * + *

You're expected to also document the {@link com.blamejared.crafttweaker.api.recipe.component.IRecipeComponent}s your + * recipe can break into. For more information, see the appropriate handler class: + * {@link com.blamejared.genericaddon.compat.crafttweaker.handlers.InfusionRecipeHandler} + * + * ## Recipe Components + * + * - `<recipecomponent:crafttweaker:input/ingredients>`, of type {@link com.blamejared.crafttweaker.api.ingredient.IIngredient} + * - `<recipecomponent:crafttweaker:output/items>`, of type {@link com.blamejared.crafttweaker.api.item.IItemStack} + * + * + */ +@Document("mods/GenericAddon/InfusionRecipe") +@NativeTypeRegistration(value = InfusionRecipe.class, zenCodeName = "mods.genericaddon.InfusionRecipe") +public class ExposeInfusionRecipe { +} diff --git a/src/main/java/com/blamejared/genericaddon/recipes/InfusionRecipe.java b/src/main/java/com/blamejared/genericaddon/recipes/InfusionRecipe.java index 469f2a5..c464c3c 100644 --- a/src/main/java/com/blamejared/genericaddon/recipes/InfusionRecipe.java +++ b/src/main/java/com/blamejared/genericaddon/recipes/InfusionRecipe.java @@ -83,6 +83,11 @@ public RecipeType getType() { return SomeGenericAddon.TYPE_INFUSION.get(); } + public Ingredient getInput(){ + //Equivalent to getIngredients().get(0) + return input; + } + @ParametersAreNonnullByDefault public static final class InfusionRecipeSerializer implements RecipeSerializer { diff --git a/src/main/resources/data/some_generic_addon/recipes/recipe_a.json b/src/main/resources/data/genericaddon/recipes/recipe_a.json similarity index 71% rename from src/main/resources/data/some_generic_addon/recipes/recipe_a.json rename to src/main/resources/data/genericaddon/recipes/recipe_a.json index 08101c4..3d04747 100644 --- a/src/main/resources/data/some_generic_addon/recipes/recipe_a.json +++ b/src/main/resources/data/genericaddon/recipes/recipe_a.json @@ -1,5 +1,5 @@ { - "type": "some_generic_addon:infusion", + "type": "genericaddon:infusion", "result": { "item": "minecraft:bedrock" }, diff --git a/src/main/resources/data/some_generic_addon/recipes/recipe_b.json b/src/main/resources/data/genericaddon/recipes/recipe_b.json similarity index 71% rename from src/main/resources/data/some_generic_addon/recipes/recipe_b.json rename to src/main/resources/data/genericaddon/recipes/recipe_b.json index 9a8a5bb..1a25ac6 100644 --- a/src/main/resources/data/some_generic_addon/recipes/recipe_b.json +++ b/src/main/resources/data/genericaddon/recipes/recipe_b.json @@ -1,5 +1,5 @@ { - "type": "some_generic_addon:infusion", + "type": "genericaddon:infusion", "result": { "item": "minecraft:redstone" }, diff --git a/src/main/resources/data/some_generic_addon/recipes/recipe_c.json b/src/main/resources/data/genericaddon/recipes/recipe_c.json similarity index 71% rename from src/main/resources/data/some_generic_addon/recipes/recipe_c.json rename to src/main/resources/data/genericaddon/recipes/recipe_c.json index 8f8710f..dce672c 100644 --- a/src/main/resources/data/some_generic_addon/recipes/recipe_c.json +++ b/src/main/resources/data/genericaddon/recipes/recipe_c.json @@ -1,5 +1,5 @@ { - "type": "some_generic_addon:infusion", + "type": "genericaddon:infusion", "result": { "item": "minecraft:iron_axe" },