Skip to content

Commit

Permalink
Add handler, recipe and example script
Browse files Browse the repository at this point in the history
  • Loading branch information
Witixin1512 committed Oct 22, 2023
1 parent 7962e33 commit 2648e17
Show file tree
Hide file tree
Showing 16 changed files with 286 additions and 17 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,6 @@ run

# Files from Forge MDK
forge*changelog.txt

# Allow
!run/scripts/test.zs
2 changes: 1 addition & 1 deletion docsOut/docs.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"nav":{"Mods":{"GenericAddon":{"InfusionRecipeManager":"mods/GenericAddon/InfusionRecipeManager.md","MyStaticIntegrationClass":"mods/GenericAddon/MyStaticIntegrationClass.md"}}}}
{"nav":{"Mods":{"GenericAddon":{"InfusionRecipe":"mods/GenericAddon/InfusionRecipe.md","InfusionRecipeManager":"mods/GenericAddon/InfusionRecipeManager.md","MyStaticIntegrationClass":"mods/GenericAddon/MyStaticIntegrationClass.md"}}}}
1 change: 1 addition & 0 deletions docsOut/docs/mods/GenericAddon/InfusionRecipe.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"path":"mods/GenericAddon/InfusionRecipe.md","zenCodeName":"mods.genericaddon.InfusionRecipe"}
29 changes: 29 additions & 0 deletions docsOut/docs/mods/GenericAddon/InfusionRecipe.md
Original file line number Diff line number Diff line change
@@ -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)>

2 changes: 1 addition & 1 deletion docsOut/docs/mods/genericaddon/InfusionRecipeManager.json
Original file line number Diff line number Diff line change
@@ -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"]}
8 changes: 4 additions & 4 deletions docsOut/docs/mods/genericaddon/InfusionRecipeManager.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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)
<recipetype:genericaddon:infusion>.addRecipe("my_recipe", <item:minecraft:diamond_pickaxe>, <item:minecraft:diamond> * 16);
<recipetype:genericaddon:infusion>.addRecipe("my_recipe", <item:minecraft:diamond> * 16, <item:minecraft:diamond_pickaxe>);
```

| 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 |


:::
Expand Down
42 changes: 42 additions & 0 deletions run/scripts/test.zs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import crafttweaker.api.ingredient.IIngredient;
import crafttweaker.api.recipe.replacement.Replacer;
import mods.genericaddon.MyStaticIntegrationClass;

MyStaticIntegrationClass.doPrint(<item:minecraft:tnt>);

<recipetype:genericaddon:infusion>.addRecipe("script_test", <item:minecraft:diamond>, <item:minecraft:dirt>);

/*
Before the Replacer call is executed. Feel free to comment the replacer out to check yourself.
[INFO][CraftTweaker-Commands]: Recipe type: '<recipetype:genericaddon:infusion>'
<recipetype:genericaddon:infusion>.addRecipe("genericaddon:recipe_a", <item:minecraft:bedrock>, <item:minecraft:iron_axe>);
<recipetype:genericaddon:infusion>.addRecipe("genericaddon:recipe_b", <item:minecraft:redstone>, <item:minecraft:bedrock>);
<recipetype:genericaddon:infusion>.addRecipe("genericaddon:recipe_c", <item:minecraft:iron_axe>, <item:minecraft:redstone>);
<recipetype:genericaddon:infusion>.addRecipe("crafttweaker:script_test", <item:minecraft:diamond>, <item:minecraft:dirt>);
*/

Replacer.create()
.replace<IIngredient>(<recipecomponent:crafttweaker:input/ingredients>, <item:minecraft:bedrock>, <item:minecraft:stone>)
.execute();

/*
After it is executed:
[INFO][CraftTweaker-Commands]: Recipe type: '<recipetype:genericaddon:infusion>'
<recipetype:genericaddon:infusion>.addRecipe("crafttweaker:autogenerated/replacer/genericaddon.recipe_b.1", <item:minecraft:redstone>, <item:minecraft:stone>);
<recipetype:genericaddon:infusion>.addRecipe("genericaddon:recipe_a", <item:minecraft:bedrock>, <item:minecraft:iron_axe>);
<recipetype:genericaddon:infusion>.addRecipe("genericaddon:recipe_c", <item:minecraft:iron_axe>, <item:minecraft:redstone>);
<recipetype:genericaddon:infusion>.addRecipe("crafttweaker:script_test", <item:minecraft:diamond>, <item:minecraft:dirt>);
*/

<recipetype:genericaddon:infusion>.removeByName("genericaddon:recipe_a");

/*
Final state:
[INFO][CraftTweaker-Commands]: Recipe type: '<recipetype:genericaddon:infusion>'
<recipetype:genericaddon:infusion>.addRecipe("crafttweaker:autogenerated/replacer/genericaddon.recipe_b.1", <item:minecraft:redstone>, <item:minecraft:stone>);
<recipetype:genericaddon:infusion>.addRecipe("genericaddon:recipe_a", <item:minecraft:bedrock>, <item:minecraft:iron_axe>);
<recipetype:genericaddon:infusion>.addRecipe("genericaddon:recipe_c", <item:minecraft:iron_axe>, <item:minecraft:redstone>);
<recipetype:genericaddon:infusion>.addRecipe("crafttweaker:script_test", <item:minecraft:diamond>, <item:minecraft:dirt>);
*/
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,6 @@ public class SomeGenericAddon {
public SomeGenericAddon() {
IEventBus modBus = FMLJavaModLoadingContext.get().getModEventBus();
SERIALIZER_REGISTRY.register(modBus);
RECIPE_REGISTRY.register(modBus);
}
}
Original file line number Diff line number Diff line change
@@ -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!
Expand Down
Original file line number Diff line number Diff line change
@@ -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<InfusionRecipe> {


/**
* Creates a String representation of a valid {@code addRecipe} (or alternative) call for the given subclass of
* {@link Recipe}.
*
* <p>Recipe dumps are triggered by the {@code /ct recipes} or {@code /ct recipes hand} commands.</p>
*
* <p>All newlines added to either the start or the end of the string will be automatically trimmed.</p>
*
* @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<? super InfusionRecipe> manager, InfusionRecipe recipe){

//Generates
// <recipetype:genericaddon:infusion>.addRecipe("genericaddon:recipe_a", <item:minecraft:iron_axe>, <item:minecraft:bedrock>);
// 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.
*
* <p>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).</p>
*
* <p>Conflicts are also considered symmetrical in this implementation, which means that if {@code firstRecipe}
* conflicts with {@code secondRecipe}, the opposite is also true.</p>
*
* @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 <strong>ensured</strong> to be of the
* same {@link RecipeType recipe type} (i.e.
* {@code firstRecipe.getType() == secondRecipe.getType()}).
* @since 9.0.0
*/
@Override
public <U extends Recipe<?>> boolean doesConflict(IRecipeManager<? super InfusionRecipe> 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}.
*
* <p>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.</p>
*
* <p>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()}.</p>
*
* <p>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.</p>
*
* @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<IDecomposedRecipe> decompose(IRecipeManager<? super InfusionRecipe> 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}.
*
* <p>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.</p>
*
* <p>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.</p>
*
* <p>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.</p>
*
* @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<InfusionRecipe> recompose(IRecipeManager<? super InfusionRecipe> 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()));
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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}
Expand Down Expand Up @@ -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 <item:minecraft:diamond_pickaxe>
* @docParam output <item:minecraft:diamond> * 16
* @docParam input <item:minecraft:diamond_pickaxe>
*/
@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));
Expand Down
Loading

0 comments on commit 2648e17

Please sign in to comment.