diff --git a/gradle.properties b/gradle.properties
index 66b6847..5f0518e 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -15,6 +15,6 @@ loader_version=0.15.6-bta.7
# halplibe_version=3.5.2
# Mod
-mod_version=5.0.0-beta.1
+mod_version=5.0.0-beta.2
mod_group=turniplabs
mod_name=halplibe
diff --git a/src/main/java/turniplabs/halplibe/helper/RecipeBuilder.java b/src/main/java/turniplabs/halplibe/helper/RecipeBuilder.java
new file mode 100644
index 0000000..e3a0ef3
--- /dev/null
+++ b/src/main/java/turniplabs/halplibe/helper/RecipeBuilder.java
@@ -0,0 +1,294 @@
+package turniplabs.halplibe.helper;
+
+import com.b100.utils.FileUtils;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.TypeAdapter;
+import com.google.gson.reflect.TypeToken;
+import net.minecraft.core.Global;
+import net.minecraft.core.WeightedRandomBag;
+import net.minecraft.core.WeightedRandomLootObject;
+import net.minecraft.core.block.Blocks;
+import net.minecraft.core.data.registry.Registries;
+import net.minecraft.core.data.registry.recipe.HasJsonAdapter;
+import net.minecraft.core.data.registry.recipe.RecipeEntryBase;
+import net.minecraft.core.data.registry.recipe.RecipeGroup;
+import net.minecraft.core.data.registry.recipe.RecipeNamespace;
+import net.minecraft.core.data.registry.recipe.RecipeSymbol;
+import net.minecraft.core.data.registry.recipe.adapter.ItemStackJsonAdapter;
+import net.minecraft.core.data.registry.recipe.adapter.RecipeJsonAdapter;
+import net.minecraft.core.data.registry.recipe.adapter.RecipeSymbolJsonAdapter;
+import net.minecraft.core.data.registry.recipe.adapter.WeightedRandomBagJsonAdapter;
+import net.minecraft.core.data.registry.recipe.adapter.WeightedRandomLootObjectJsonAdapter;
+import net.minecraft.core.item.IItemConvertible;
+import net.minecraft.core.item.ItemStack;
+import org.jetbrains.annotations.NotNull;
+import turniplabs.halplibe.helper.recipeBuilders.RecipeBuilderBlastFurnace;
+import turniplabs.halplibe.helper.recipeBuilders.RecipeBuilderFurnace;
+import turniplabs.halplibe.helper.recipeBuilders.RecipeBuilderShaped;
+import turniplabs.halplibe.helper.recipeBuilders.RecipeBuilderShapeless;
+import turniplabs.halplibe.helper.recipeBuilders.RecipeBuilderTrommel;
+import turniplabs.halplibe.helper.recipeBuilders.modifiers.BlastFurnaceModifier;
+import turniplabs.halplibe.helper.recipeBuilders.modifiers.FurnaceModifier;
+import turniplabs.halplibe.helper.recipeBuilders.modifiers.TrommelModifier;
+import turniplabs.halplibe.helper.recipeBuilders.modifiers.WorkbenchModifier;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+public final class RecipeBuilder {
+ /**
+ * Initializes vanilla work stations for a specified mod id.
+ * @param modID ModID to initialize
+ */
+ @SuppressWarnings("unused")
+ public static void initNameSpace(String modID){
+ getRecipeNamespace(modID);
+ RecipeBuilder.getRecipeGroup(modID, "blast_furnace", new RecipeSymbol(Blocks.FURNACE_BLAST_ACTIVE.getDefaultStack()));
+ RecipeBuilder.getRecipeGroup(modID, "furnace", new RecipeSymbol(Blocks.FURNACE_STONE_ACTIVE.getDefaultStack()));
+ RecipeBuilder.getRecipeGroup(modID, "workbench", new RecipeSymbol(Blocks.WORKBENCH.getDefaultStack()));
+ RecipeBuilder.getRecipeGroup(modID, "trommel", new RecipeSymbol(Blocks.TROMMEL_ACTIVE.getDefaultStack()));
+ }
+
+ /**
+ * @param modID modId for namespace (or 'minecraft' for vanilla namespace)
+ * @return Returns existing recipeNamespace if it already exists, or creates one and returns it if it does not exist.
+ */
+ @NotNull
+ public static RecipeNamespace getRecipeNamespace(String modID){
+ if (Registries.RECIPES.getItem(modID) != null){
+ return Registries.RECIPES.getItem(modID);
+ }
+ RecipeNamespace modSpace = new RecipeNamespace();
+ Registries.RECIPES.register(modID, modSpace);
+ return Objects.requireNonNull(modSpace);
+ }
+
+ /**
+ * @param modID modId for namespace (or 'minecraft' for vanilla namespace)
+ * @param key recipeGroup key
+ * @param symbol {@link RecipeSymbol} which represents the workstation
+ * @return {@link RecipeGroup} Returns existing RecipeGroup if it already exists, or creates one and returns it if it does not exist.
+ */
+ @NotNull
+ public static RecipeGroup> getRecipeGroup(String modID, String key, RecipeSymbol symbol){
+ return getRecipeGroup(getRecipeNamespace(modID), key, symbol);
+ }
+ /**
+ * @param namespace {@link RecipeNamespace} which contains the {@link RecipeGroup}
+ * @param key recipeGroup key
+ * @param symbol {@link RecipeSymbol} which represents the workstation
+ * @return {@link RecipeGroup} Returns existing RecipeGroup if it already exists, or creates one and returns it if it does not exist.
+ */
+ @NotNull
+ public static RecipeGroup> getRecipeGroup(RecipeNamespace namespace, String key, RecipeSymbol symbol){
+ if (namespace.getItem(key) != null){
+ return namespace.getItem(key);
+ }
+ RecipeGroup> group = new RecipeGroup<>(symbol);
+ namespace.register(key, group);
+ return Objects.requireNonNull(group);
+ }
+
+ /**
+ * Gets or creates a specified ItemGroup.
+ *
+ * Example code:
+ *
{@code
+ * RecipeBuilder.getItemGroup("minecraft", "logs")
+ * }
+ *
+ * @param modID Mod that owns the ItemGroup (or 'minecraft' for vanilla groups)
+ * @param key Group key
+ * @return Returns the existing ItemGroup if it exists, or create one and return that if it doesn't yet exist.
+ */
+ @NotNull
+ public static List getItemGroup(String modID, String key){
+ List group = Registries.ITEM_GROUPS.getItem(String.format("%s:%s", modID, key));
+ if (group == null){
+ group = new ArrayList<>();
+ Registries.ITEM_GROUPS.register(String.format("%s:%s", modID, key), group);
+ }
+ return group;
+ }
+
+ /**
+ * Adds specified items to an ItemGroup. If the group does not exist before the method is called then it will be created.
+ *
+ * Example code:
+ * {@code
+ * RecipeBuilder.addItemsToGroup("minecraft", "cobblestones",
+ * Block.cobbleStone,
+ * Block.cobbleBasalt,
+ * Block.cobbleLimestone,
+ * Block.cobbleGranite,
+ * Block.cobblePermafrost)
+ * }
+ *
+ * @param modID Mod that owns the ItemGroup (or 'minecraft' for vanilla groups)
+ * @param key Group key
+ * @param items List of only {@link IItemConvertible} (which includes Blocks/Items) and {@link ItemStack}
+ */
+ @SuppressWarnings("unused")
+ public static void addItemsToGroup(String modID, String key, Object ... items ){
+ List group = getItemGroup(modID, key);
+ for (Object o : items){
+ if (o instanceof IItemConvertible){
+ group.add(((IItemConvertible) o).getDefaultStack());
+ continue;
+ }
+ if (o instanceof ItemStack){
+ group.add((ItemStack) o);
+ continue;
+ }
+ throw new IllegalArgumentException(String.format("Object '%s' has invalid class '%s'! Only classes that extend 'IItemConvertible' or 'ItemStack' are allowed!", o.toString(), o.getClass().getSimpleName()));
+ }
+ }
+
+ /**
+ * Returns a new {@link RecipeBuilderShaped}
+ * Used for creating new shaped workbench recipes.
+ */
+ @SuppressWarnings("unused")
+ public static RecipeBuilderShaped Shaped(String modID){
+ return new RecipeBuilderShaped(modID);
+ }
+
+ /**
+ * Returns a new {@link RecipeBuilderShaped} with its shape set
+ * Used for creating new shaped workbench recipes.
+ */
+ @SuppressWarnings("unused")
+ public static RecipeBuilderShaped Shaped(String modID, String... shape){
+ return new RecipeBuilderShaped(modID, shape);
+ }
+
+ /**
+ * Returns a new {@link RecipeBuilderShapeless}
+ * Used for creating new shapeless workbench recipes
+ */
+ @SuppressWarnings("unused")
+ public static RecipeBuilderShapeless Shapeless(String modID){
+ return new RecipeBuilderShapeless(modID);
+ }
+
+ /**
+ * Returns a new {@link RecipeBuilderFurnace}
+ * Used for creating new furnace recipes.
+ */
+ @SuppressWarnings("unused")
+ public static RecipeBuilderFurnace Furnace(String modID){
+ return new RecipeBuilderFurnace(modID);
+ }
+
+ /**
+ * Returns a new {@link RecipeBuilderBlastFurnace}
+ * Used for creating new blast furnace recipes.
+ */
+ @SuppressWarnings("unused")
+ public static RecipeBuilderBlastFurnace BlastFurnace(String modID){
+ return new RecipeBuilderBlastFurnace(modID);
+ }
+
+ /**
+ * Returns a new {@link RecipeBuilderTrommel}
+ * Used for creating new trommel recipes.
+ */
+ @SuppressWarnings("unused")
+ public static RecipeBuilderTrommel Trommel(String modID){
+ return new RecipeBuilderTrommel(modID);
+ }
+
+ /**
+ * Returns a new {@link TrommelModifier}
+ * Used for modifying existing trommel recipes.
+ */
+ @SuppressWarnings("unused")
+ public static TrommelModifier ModifyTrommel(String namespace, String key){
+ return new TrommelModifier(namespace, key);
+ }
+
+ /**
+ * Returns a new {@link WorkbenchModifier}
+ * Used for modifying existing workbench recipes.
+ */
+ @SuppressWarnings("unused")
+ public static WorkbenchModifier ModifyWorkbench(String namespace){
+ return new WorkbenchModifier(namespace);
+ }
+
+ /**
+ * Returns a new {@link FurnaceModifier}
+ * Used for modifying existing furnace recipes.
+ */
+ @SuppressWarnings("unused")
+ public static FurnaceModifier ModifyFurnace(String namespace){
+ return new FurnaceModifier(namespace);
+ }
+
+ /**
+ * Returns a new {@link BlastFurnaceModifier}
+ * Used for modifying existing blast furnace recipes.
+ */
+ @SuppressWarnings("unused")
+ public static BlastFurnaceModifier ModifyBlastFurnace(String namespace){
+ return new BlastFurnaceModifier(namespace);
+ }
+ public static boolean isExporting = false;
+
+ /**
+ * Serializes all loaded recipes to json and dumps the output to ".minecraft-bta/recipeDump/recipes.json"
+ */
+ @SuppressWarnings("unchecked")
+ public static void exportRecipes(){
+ isExporting = true;
+ Path filePath = Paths.get(Global.accessor.getMinecraftDir() + "/" + "recipeDump");
+ createDir(filePath);
+ String path = filePath + "/recipes.json";
+ List> recipes = Registries.RECIPES.getAllSerializableRecipes();
+ GsonBuilder builder = new GsonBuilder();
+ builder.setPrettyPrinting();
+ ArrayList> usedAdapters = new ArrayList<>();
+ for (RecipeEntryBase, ?, ?> recipe : recipes) {
+ HasJsonAdapter hasJsonAdapter = (HasJsonAdapter) recipe;
+ RecipeJsonAdapter> recipeJsonAdapter = hasJsonAdapter.getAdapter();
+ if (usedAdapters.contains(recipeJsonAdapter)) continue;
+ builder.registerTypeAdapter(recipe.getClass(), recipeJsonAdapter);
+ usedAdapters.add(recipeJsonAdapter);
+ }
+ builder.registerTypeAdapter(ItemStack.class, new ItemStackJsonAdapter());
+ builder.registerTypeAdapter(RecipeSymbol.class, new RecipeSymbolJsonAdapter());
+ builder.registerTypeAdapter(new TypeToken>(){}.getType(), new WeightedRandomBagJsonAdapter());
+ builder.registerTypeAdapter(WeightedRandomLootObject.class, new WeightedRandomLootObjectJsonAdapter());
+ Gson gson = builder.create();
+ JsonArray jsonArray = new JsonArray();
+ for (RecipeEntryBase, ?, ?> recipeEntryBase : recipes) {
+ TypeAdapter> typeAdapter = (TypeAdapter>) gson.getAdapter(recipeEntryBase.getClass());
+ JsonElement json = typeAdapter.toJsonTree(recipeEntryBase);
+ jsonArray.add(json);
+ }
+ File file = FileUtils.createNewFile(new File(path));
+ try (FileWriter fileWriter = new FileWriter(file)){
+ gson.toJson(jsonArray, fileWriter);
+ } catch (IOException iOException) {
+ throw new RuntimeException(iOException);
+ }
+ isExporting = false;
+ }
+ private static void createDir(Path path){
+ try {
+ Files.createDirectories(path);
+ } catch (IOException e) {
+ System.err.println("Failed to create directory!" + e.getMessage());
+ }
+ }
+}
diff --git a/src/main/java/turniplabs/halplibe/helper/recipeBuilders/RecipeBuilderBase.java b/src/main/java/turniplabs/halplibe/helper/recipeBuilders/RecipeBuilderBase.java
new file mode 100644
index 0000000..5c47cc4
--- /dev/null
+++ b/src/main/java/turniplabs/halplibe/helper/recipeBuilders/RecipeBuilderBase.java
@@ -0,0 +1,44 @@
+package turniplabs.halplibe.helper.recipeBuilders;
+
+import net.minecraft.core.item.ItemStack;
+import net.minecraft.core.item.IItemConvertible;
+
+import java.util.Objects;
+
+public abstract class RecipeBuilderBase implements Cloneable {
+ protected String modID;
+ public RecipeBuilderBase(String modID){
+ this.modID = Objects.requireNonNull(modID, "ModID must not be null!");
+ }
+ @SuppressWarnings({"unchecked", "unused"})
+ public T clone(T object){
+ return (T) clone();
+ }
+ @Override
+ public RecipeBuilderBase clone() {
+ try {
+ // none of the fields are mutated so this should be fine
+ return (RecipeBuilderBase) super.clone();
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError();
+ }
+ }
+
+ /**
+ * Creates a new recipe from the provided builder arguments.
+ * @param recipeID Recipe identifier to assign to the created recipe
+ * @param output Result of crafting the specified recipe
+ */
+ @SuppressWarnings({"unused"})
+ public void create(String recipeID, IItemConvertible output) {
+ create(recipeID, output.getDefaultStack());
+ }
+
+ /**
+ * Creates a new recipe from the provided builder arguments.
+ * @param recipeID Recipe identifier to assign to the created recipe
+ * @param outputStack Result of crafting the specified recipe
+ */
+ @SuppressWarnings({"unused"})
+ public abstract void create(String recipeID, ItemStack outputStack);
+}
diff --git a/src/main/java/turniplabs/halplibe/helper/recipeBuilders/RecipeBuilderBlastFurnace.java b/src/main/java/turniplabs/halplibe/helper/recipeBuilders/RecipeBuilderBlastFurnace.java
new file mode 100644
index 0000000..ed61abd
--- /dev/null
+++ b/src/main/java/turniplabs/halplibe/helper/recipeBuilders/RecipeBuilderBlastFurnace.java
@@ -0,0 +1,24 @@
+package turniplabs.halplibe.helper.recipeBuilders;
+
+import net.minecraft.core.block.Blocks;
+import net.minecraft.core.data.registry.recipe.RecipeGroup;
+import net.minecraft.core.data.registry.recipe.RecipeSymbol;
+import net.minecraft.core.data.registry.recipe.entry.RecipeEntryBlastFurnace;
+import net.minecraft.core.item.ItemStack;
+import turniplabs.halplibe.helper.RecipeBuilder;
+
+public class RecipeBuilderBlastFurnace extends RecipeBuilderFurnace{
+ /**
+ * Used for creating new blast furnace recipes.
+ * @param modID Namespace to create recipe under
+ */
+ public RecipeBuilderBlastFurnace(String modID) {
+ super(modID);
+ }
+ @Override
+ @SuppressWarnings({"unchecked", "unused"})
+ public void create(String recipeID, ItemStack outputStack) {
+ ((RecipeGroup< RecipeEntryBlastFurnace>) RecipeBuilder.getRecipeGroup(modID, "blast_furnace", new RecipeSymbol(Blocks.FURNACE_BLAST_ACTIVE.getDefaultStack())))
+ .register(recipeID, new RecipeEntryBlastFurnace(input, outputStack));
+ }
+}
diff --git a/src/main/java/turniplabs/halplibe/helper/recipeBuilders/RecipeBuilderFurnace.java b/src/main/java/turniplabs/halplibe/helper/recipeBuilders/RecipeBuilderFurnace.java
new file mode 100644
index 0000000..187ad12
--- /dev/null
+++ b/src/main/java/turniplabs/halplibe/helper/recipeBuilders/RecipeBuilderFurnace.java
@@ -0,0 +1,83 @@
+package turniplabs.halplibe.helper.recipeBuilders;
+
+import net.minecraft.core.block.Blocks;
+import net.minecraft.core.data.registry.recipe.RecipeGroup;
+import net.minecraft.core.data.registry.recipe.RecipeSymbol;
+import net.minecraft.core.data.registry.recipe.entry.RecipeEntryFurnace;
+import net.minecraft.core.item.IItemConvertible;
+import net.minecraft.core.item.ItemStack;
+import turniplabs.halplibe.helper.RecipeBuilder;
+
+import java.util.Objects;
+
+public class RecipeBuilderFurnace extends RecipeBuilderBase{
+ protected RecipeSymbol input;
+ /**
+ * Used for creating new furnace recipes.
+ * @param modID Namespace to create recipe under
+ */
+ public RecipeBuilderFurnace(String modID) {
+ super(modID);
+ }
+
+ /**
+ * Furnace recipes can only have one input
+ * @param input Input item
+ * @return Copy of {@link RecipeBuilderFurnace}
+ */
+ @SuppressWarnings({"unused"})
+ public RecipeBuilderFurnace setInput(IItemConvertible input){
+ return setInput(input, 0);
+ }
+
+ /**
+ * Furnace recipes can only have one input
+ * @param input Input item
+ * @param meta Item's required metadata
+ * @return Copy of {@link RecipeBuilderFurnace}
+ */
+ @SuppressWarnings({"unused"})
+ public RecipeBuilderFurnace setInput(IItemConvertible input, int meta){
+ return setInput(new ItemStack(input, 1, meta));
+ }
+
+ /**
+ * Furnace recipes can only have one input
+ * @param input Input {@link ItemStack}
+ * @return Copy of {@link RecipeBuilderFurnace}
+ */
+ @SuppressWarnings({"unused"})
+ public RecipeBuilderFurnace setInput(ItemStack input){
+ return setInput(new RecipeSymbol(input));
+ }
+
+ /**
+ * Furnace recipes can only have one input
+ * @param itemGroup Input item group
+ * @return Copy of {@link RecipeBuilderFurnace}
+ */
+ @SuppressWarnings({"unused"})
+ public RecipeBuilderFurnace setInput(String itemGroup){
+ return setInput(new RecipeSymbol(itemGroup));
+ }
+
+ /**
+ * Furnace recipes can only have one input
+ * @param input {@link RecipeSymbol} Input symbol
+ * @return Copy of {@link RecipeBuilderFurnace}
+ */
+ @SuppressWarnings({"unused"})
+ public RecipeBuilderFurnace setInput(RecipeSymbol input){
+ RecipeBuilderFurnace builder = this.clone(this);
+ builder.input = Objects.requireNonNull(input, "Input symbol must not be null!");
+ return builder;
+ }
+
+ @Override
+ @SuppressWarnings({"unchecked"})
+ public void create(String recipeID, ItemStack outputStack) {
+ Objects.requireNonNull(input, "Input symbol must not be null!");
+ ((RecipeGroup) RecipeBuilder.getRecipeGroup(modID, "furnace", new RecipeSymbol(Blocks.FURNACE_STONE_ACTIVE.getDefaultStack())))
+ .register(recipeID, new RecipeEntryFurnace(input, outputStack));
+ }
+}
diff --git a/src/main/java/turniplabs/halplibe/helper/recipeBuilders/RecipeBuilderShaped.java b/src/main/java/turniplabs/halplibe/helper/recipeBuilders/RecipeBuilderShaped.java
new file mode 100644
index 0000000..2b59502
--- /dev/null
+++ b/src/main/java/turniplabs/halplibe/helper/recipeBuilders/RecipeBuilderShaped.java
@@ -0,0 +1,211 @@
+package turniplabs.halplibe.helper.recipeBuilders;
+
+import net.minecraft.core.block.Blocks;
+import net.minecraft.core.data.registry.recipe.RecipeGroup;
+import net.minecraft.core.data.registry.recipe.RecipeSymbol;
+import net.minecraft.core.data.registry.recipe.entry.RecipeEntryCrafting;
+import net.minecraft.core.data.registry.recipe.entry.RecipeEntryCraftingShaped;
+import net.minecraft.core.item.IItemConvertible;
+import net.minecraft.core.item.ItemStack;
+import turniplabs.halplibe.helper.RecipeBuilder;
+
+import java.util.Arrays;
+import java.util.HashMap;
+
+public class RecipeBuilderShaped extends RecipeBuilderBase{
+ protected String[] shape;
+ protected int width;
+ protected int height;
+ protected boolean consumeContainer = false;
+ protected final HashMap symbolShapedMap = new HashMap<>();
+ /**
+ * Used for creating new shaped workbench recipes.
+ * @param modID Namespace to create recipe under
+ */
+ public RecipeBuilderShaped(String modID){
+ super(modID);
+ }
+ /**
+ * Used for creating new shaped workbench recipes.
+ * @param modID Namespace to create recipe under
+ * @param shape Recipe shape in symbol representation
+ */
+ public RecipeBuilderShaped(String modID, String... shape) {
+ super(modID);
+ setShapeLocal(shape);
+ }
+
+ /**
+ * Sets the shape of the recipe
+ * Example code:
+ * {@code
+ * RecipeBuilderShaped("minecraft")
+ * .setShape(
+ * "PPP",
+ * "P P",
+ * "PPP")
+ * .addInput('P', Block.planksOak)
+ * .create("chest_planks_oak", Block.chestPlanksOak.getDefaultStack());
+ * }
+ * @param shapeTemplate Recipe shape in symbol representation
+ * @return Copy of {@link RecipeBuilderShaped}
+ */
+ @SuppressWarnings({"unused"})
+ public RecipeBuilderShaped setShape(String... shapeTemplate){
+ RecipeBuilderShaped builder = this.clone(this);
+ builder.setShapeLocal(shapeTemplate);
+ return builder;
+ }
+ protected void setShapeLocal(String... shape){
+ if (shape == null){
+ throw new IllegalArgumentException("Shape Template cannot be set to null!");
+ }
+ if (shape.length == 0){
+ throw new IllegalArgumentException("Shape Template cannot have a size of 0!");
+ }
+ if (shape.length > 3){
+ throw new IllegalArgumentException("Shape Template height cannot exceed 3!\n" + Arrays.toString(shape));
+ }
+ if (shape[0].length() > 3){
+ throw new IllegalArgumentException("Shape Template width cannot exceed 3!\n" + Arrays.toString(shape));
+ }
+ this.height = shape.length;
+ this.width = shape[0].length();
+
+ // Gets the max width
+ for (int y = 0; y < this.height; y++) {
+ this.width = Math.max(this.width, shape[y].length());
+ }
+
+ // Ensures that the recipe shape is always square
+ String[] internalShape = new String[height];
+ for (int y = 0; y < internalShape.length; y++) {
+ StringBuilder builder = new StringBuilder();
+ String row = shape[y];
+ for (int x = 0; x < width; x++) {
+ if (x >= row.length()){
+ builder.append(" ");
+ } else {
+ builder.append(row.charAt(x));
+ }
+ }
+ internalShape[y] = builder.toString();
+ }
+
+ this.shape = internalShape;
+ }
+
+ /**
+ * Specifies whether the recipe should consume container items.
+ * {@code
+ * RecipeBuilderShaped("minecraft")
+ * .setShape(
+ * "BBB",
+ * "SES",
+ * "WWW")
+ * .addInput('B', Item.bucketMilk)
+ * .addInput('S', Item.dustSugar)
+ * .addInput('E', Item.eggChicken)
+ * .addInput('W', Item.wheat)
+ * .setConsumeContainer(false) // Recipe will return empty buckets when crafted
+ * .create("cake", Item.cake.getDefaultStack());
+ * }
+ * @param consumeContainer Should consume ContainerItem
+ * @return Copy of {@link RecipeBuilderShaped}
+ */
+ @SuppressWarnings({"unused"})
+ public RecipeBuilderShaped setConsumeContainer(boolean consumeContainer){
+ RecipeBuilderShaped builder = this.clone(this);
+ builder.consumeContainer = consumeContainer;
+ return builder;
+ }
+
+ /**
+ * Assigns an item to an item symbol defined in the shape (see {@link #setShape(String...)})
+ * @param templateSymbol Item symbol character
+ * @param stack Stack to assign
+ * @return Copy of {@link RecipeBuilderShaped}
+ */
+ @SuppressWarnings({"unused"})
+ public RecipeBuilderShaped addInput(char templateSymbol, IItemConvertible stack){
+ return addInput(templateSymbol, stack, 0);
+ }
+
+ /**
+ * Assigns an item to an item symbol defined in the shape (see {@link #setShape(String...)})
+ * @param templateSymbol Item symbol character
+ * @param stack Stack to assign
+ * @return Copy of {@link RecipeBuilderShaped}
+ */
+ @SuppressWarnings({"unused"})
+ public RecipeBuilderShaped addInput(char templateSymbol, IItemConvertible stack, int meta){
+ ItemStack _stack = stack.getDefaultStack();
+ _stack.setMetadata(meta);
+ return addInput(templateSymbol, _stack);
+ }
+
+ /**
+ * Assigns an item to an item symbol defined in the shape (see {@link #setShape(String...)})
+ * @param templateSymbol Item symbol character
+ * @param stack Stack to assign
+ * @return Copy of {@link RecipeBuilderShaped}
+ */
+ @SuppressWarnings({"unused"})
+ public RecipeBuilderShaped addInput(char templateSymbol, ItemStack stack){
+ return addInput(templateSymbol, new RecipeSymbol(stack));
+ }
+
+ /**
+ * Assigns an itemGroup to an item symbol defined in the shape (see {@link #setShape(String...)})
+ * @param templateSymbol Item symbol character
+ * @param itemGroup ItemGroup key to assign
+ * @return Copy of {@link RecipeBuilderShaped}
+ */
+ @SuppressWarnings({"unused"})
+ public RecipeBuilderShaped addInput(char templateSymbol, String itemGroup) {
+ return addInput(templateSymbol, new RecipeSymbol(itemGroup));
+ }
+
+ /**
+ * Assigns an item to an item symbol defined in the shape (see {@link #setShape(String...)})
+ * @param templateSymbol Item symbol character
+ * @param symbol {@link RecipeSymbol} to assign
+ * @return Copy of {@link RecipeBuilderShaped}
+ */
+ @SuppressWarnings({"unused"})
+ public RecipeBuilderShaped addInput(char templateSymbol, RecipeSymbol symbol){
+ if (templateSymbol == ' ') throw new IllegalArgumentException("Cannot assign item to protected symbol ' ' pick a different symbol for your recipe input");
+ RecipeBuilderShaped builder = this.clone(this);
+ symbolShapedMap.put(templateSymbol, symbol);
+ return builder;
+ }
+
+ @SuppressWarnings({"unchecked", "unused"})
+ public void create(String recipeID, ItemStack outputStack) {
+ if (shape == null) throw new RuntimeException("Shaped recipe: " + recipeID + " attempted to build without a assigned shape!!");
+ RecipeSymbol[] recipe = new RecipeSymbol[height * width];
+ for (int x = 0; x < width; x++) {
+ for (int y = 0; y < height; y++) {
+ Character cha = null;
+ if (shape[y].length() > x) {
+ cha = shape[y].charAt(x);
+ }
+ RecipeSymbol tempplate = symbolShapedMap.get(cha);
+ if (tempplate == null){
+ recipe[x + y * width] = null;
+ } else {
+ if(tempplate.getItemGroup() == null){
+ RecipeSymbol s = new RecipeSymbol(cha == null ? ' ' : cha, tempplate.getStack());
+ recipe[x + y * width] = s;
+ } else {
+ recipe[x + y * width] = new RecipeSymbol(cha == null ? ' ' : cha, tempplate.getStack(), tempplate.getItemGroup());
+ }
+
+ }
+
+ }
+ }
+ ((RecipeGroup>) RecipeBuilder.getRecipeGroup(modID, "workbench", new RecipeSymbol(Blocks.WORKBENCH.getDefaultStack())))
+ .register(recipeID, new RecipeEntryCraftingShaped(width, height, recipe, outputStack, consumeContainer));
+ }
+}
diff --git a/src/main/java/turniplabs/halplibe/helper/recipeBuilders/RecipeBuilderShapeless.java b/src/main/java/turniplabs/halplibe/helper/recipeBuilders/RecipeBuilderShapeless.java
new file mode 100644
index 0000000..3b89584
--- /dev/null
+++ b/src/main/java/turniplabs/halplibe/helper/recipeBuilders/RecipeBuilderShapeless.java
@@ -0,0 +1,80 @@
+package turniplabs.halplibe.helper.recipeBuilders;
+
+import net.minecraft.core.block.Blocks;
+import net.minecraft.core.data.registry.recipe.RecipeGroup;
+import net.minecraft.core.data.registry.recipe.RecipeSymbol;
+import net.minecraft.core.data.registry.recipe.entry.RecipeEntryCrafting;
+import net.minecraft.core.data.registry.recipe.entry.RecipeEntryCraftingShapeless;
+import net.minecraft.core.item.IItemConvertible;
+import net.minecraft.core.item.ItemStack;
+import turniplabs.halplibe.helper.RecipeBuilder;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class RecipeBuilderShapeless extends RecipeBuilderBase{
+ private final List symbolShapelessList = new ArrayList<>();
+ /**
+ * Used for creating new shapeless workbench recipes.
+ * @param modID Namespace to create recipe under
+ */
+ public RecipeBuilderShapeless(String modID) {
+ super(modID);
+ }
+
+ /**
+ * @param stack Item to add to recipe's item list
+ * @return Copy of {@link RecipeBuilderShapeless}
+ */
+ @SuppressWarnings({"unused"})
+ public RecipeBuilderShapeless addInput(IItemConvertible stack){
+ return addInput(stack, 0);
+ }
+
+ /**
+ * @param stack Item to add to recipe's item list
+ * @param meta Required meta of the item
+ * @return Copy of {@link RecipeBuilderShapeless}
+ */
+ @SuppressWarnings({"unused"})
+ public RecipeBuilderShapeless addInput(IItemConvertible stack, int meta){
+ ItemStack _stack = stack.getDefaultStack();
+ _stack.setMetadata(meta);
+ return addInput(_stack);
+ }
+
+ /**
+ * @param itemGroup ItemGroup to add to recipe's item list
+ * @return Copy of {@link RecipeBuilderShapeless}
+ */
+ @SuppressWarnings({"unused"})
+ public RecipeBuilderShapeless addInput(String itemGroup){
+ return addInput(new RecipeSymbol(itemGroup));
+ }
+
+ /**
+ * @param stack Item to add to recipe's item list
+ * @return Copy of {@link RecipeBuilderShapeless}
+ */
+ @SuppressWarnings({"unused"})
+ public RecipeBuilderShapeless addInput(ItemStack stack){
+ return addInput(new RecipeSymbol(stack));
+ }
+
+ /**
+ * @param symbol {@link RecipeSymbol} to add to recipe's item list
+ * @return Copy of {@link RecipeBuilderShapeless}
+ */
+ @SuppressWarnings({"unused"})
+ public RecipeBuilderShapeless addInput(RecipeSymbol symbol){
+ RecipeBuilderShapeless builder = this.clone(this);
+ symbolShapelessList.add(symbol);
+ return builder;
+ }
+ @Override
+ @SuppressWarnings({"unused", "unchecked"})
+ public void create(String recipeID, ItemStack outputStack) {
+ ((RecipeGroup>) RecipeBuilder.getRecipeGroup(modID, "workbench", new RecipeSymbol(Blocks.WORKBENCH.getDefaultStack())))
+ .register(recipeID, new RecipeEntryCraftingShapeless(symbolShapelessList, outputStack));
+ }
+}
diff --git a/src/main/java/turniplabs/halplibe/helper/recipeBuilders/RecipeBuilderTrommel.java b/src/main/java/turniplabs/halplibe/helper/recipeBuilders/RecipeBuilderTrommel.java
new file mode 100644
index 0000000..41cbf53
--- /dev/null
+++ b/src/main/java/turniplabs/halplibe/helper/recipeBuilders/RecipeBuilderTrommel.java
@@ -0,0 +1,120 @@
+package turniplabs.halplibe.helper.recipeBuilders;
+
+import net.minecraft.core.WeightedRandomBag;
+import net.minecraft.core.WeightedRandomLootObject;
+import net.minecraft.core.block.Blocks;
+import net.minecraft.core.data.registry.recipe.RecipeGroup;
+import net.minecraft.core.data.registry.recipe.RecipeSymbol;
+import net.minecraft.core.data.registry.recipe.entry.RecipeEntryTrommel;
+import net.minecraft.core.item.IItemConvertible;
+import net.minecraft.core.item.ItemStack;
+import turniplabs.halplibe.helper.RecipeBuilder;
+
+import java.util.Objects;
+
+public class RecipeBuilderTrommel extends RecipeBuilderBase{
+ protected RecipeSymbol input;
+ protected WeightedRandomBag bag = new WeightedRandomBag<>();
+ /**
+ * Used for creating new trommel recipes.
+ * @param modID Namespace to create recipe under
+ */
+ public RecipeBuilderTrommel(String modID) {
+ super(modID);
+ }
+
+ /**
+ * Trommel recipes can only have one input
+ * @param item Input item
+ * @return Copy of {@link RecipeBuilderTrommel}
+ */
+ @SuppressWarnings({"unused"})
+ public RecipeBuilderTrommel setInput(IItemConvertible item){
+ return setInput(item, 0);
+ }
+
+ /**
+ * Trommel recipes can only have one input
+ * @param item Input item
+ * @param meta Required metadata of input item
+ * @return Copy of {@link RecipeBuilderTrommel}
+ */
+ @SuppressWarnings({"unused"})
+ public RecipeBuilderTrommel setInput(IItemConvertible item, int meta){
+ return setInput(new ItemStack(item, 1, meta));
+ }
+
+ /**
+ * Trommel recipes can only have one input
+ * @param input Input {@link ItemStack}
+ * @return Copy of {@link RecipeBuilderTrommel}
+ */
+ @SuppressWarnings({"unused"})
+ public RecipeBuilderTrommel setInput(ItemStack input){
+ return setInput(new RecipeSymbol(input));
+ }
+
+ /**
+ * Trommel recipes can only have one input
+ * @param itemGroup Input itemGroup
+ * @return Copy of {@link RecipeBuilderTrommel}
+ */
+ @SuppressWarnings({"unused"})
+ public RecipeBuilderTrommel setInput(String itemGroup){
+ return setInput(new RecipeSymbol(itemGroup));
+ }
+
+ /**
+ * Trommel recipes can only have one input
+ * @param input Input {@link RecipeSymbol}
+ * @return Copy of {@link RecipeBuilderTrommel}
+ */
+ @SuppressWarnings({"unused"})
+ public RecipeBuilderTrommel setInput(RecipeSymbol input){
+ RecipeBuilderTrommel builder = this.clone(this);
+ builder.input = Objects.requireNonNull(input, "Input symbol must not be null!");
+ return builder;
+ }
+
+ /**
+ * Adds a potential output entry for the recipe
+ * {@code
+ * RecipeBuilderTrommel("minecraft")
+ * .setInput("minecraft:dirt")
+ * .addEntry(new WeightedRandomLootObject(Item.ammoPebble.getDefaultStack(), 1, 3), 60.24)
+ * .addEntry(new WeightedRandomLootObject(Item.clay.getDefaultStack()), 24.1)
+ * .addEntry(new WeightedRandomLootObject(Item.flint.getDefaultStack()), 12.05)
+ * .addEntry(new WeightedRandomLootObject(Item.sulphur.getDefaultStack()), 2.41)
+ * .addEntry(new WeightedRandomLootObject(Item.oreRawIron.getDefaultStack()), 0.6)
+ * .addEntry(new WeightedRandomLootObject(Item.olivine.getDefaultStack()), 0.3)
+ * .addEntry(new WeightedRandomLootObject(Item.quartz.getDefaultStack()), 0.3)
+ * .create("dirt");
+ * }
+ * @param lootObject {@link WeightedRandomLootObject} provides possible outs
+ * @param weight Comparative probability that this loot object will be selected, higher weights means more likely
+ * @return Copy of {@link RecipeBuilderTrommel}
+ */
+ @SuppressWarnings({"unused"})
+ public RecipeBuilderTrommel addEntry(WeightedRandomLootObject lootObject, double weight){
+ RecipeBuilderTrommel builder = this.clone(this);
+ builder.bag.addEntry(lootObject, weight);
+ return builder;
+ }
+
+ /**
+ * Creates a new recipe from the provided builder arguments.
+ * @param recipeID Recipe identifier to assign to the created recipe
+ */
+ @SuppressWarnings({"unused", "unchecked"})
+ public void create(String recipeID) {
+ Objects.requireNonNull(input, "Input symbol must not be null!");
+ Objects.requireNonNull(bag, "Weighted Bag must not be null!");
+ ((RecipeGroup) RecipeBuilder.getRecipeGroup(modID, "trommel", new RecipeSymbol(Blocks.TROMMEL_ACTIVE.getDefaultStack())))
+ .register(recipeID, new RecipeEntryTrommel(input, bag));
+ }
+ @Override
+ public void create(String recipeID, ItemStack outputStack) throws IllegalArgumentException {
+ // Standard create method doesn't apply to this class
+ throw new IllegalArgumentException("Use create(String recipeID), create(String recipeID, ItemStack outputStack) does not apply for trommels");
+ }
+}
diff --git a/src/main/java/turniplabs/halplibe/helper/recipeBuilders/modifiers/BlastFurnaceModifier.java b/src/main/java/turniplabs/halplibe/helper/recipeBuilders/modifiers/BlastFurnaceModifier.java
new file mode 100644
index 0000000..76ce0b4
--- /dev/null
+++ b/src/main/java/turniplabs/halplibe/helper/recipeBuilders/modifiers/BlastFurnaceModifier.java
@@ -0,0 +1,20 @@
+package turniplabs.halplibe.helper.recipeBuilders.modifiers;
+
+import net.minecraft.core.block.Blocks;
+import net.minecraft.core.data.registry.recipe.RecipeGroup;
+import net.minecraft.core.data.registry.recipe.RecipeSymbol;
+import net.minecraft.core.data.registry.recipe.entry.RecipeEntryBlastFurnace;
+import turniplabs.halplibe.helper.RecipeBuilder;
+
+public class BlastFurnaceModifier {
+ protected RecipeGroup recipeGroup;
+ @SuppressWarnings("unchecked")
+ public BlastFurnaceModifier(String namespace){
+ recipeGroup = (RecipeGroup) RecipeBuilder.getRecipeGroup(namespace, "blast_furnace", new RecipeSymbol(Blocks.FURNACE_BLAST_ACTIVE.getDefaultStack()));
+ }
+ @SuppressWarnings({"unchecked", "unused"})
+ public BlastFurnaceModifier removeRecipe(String recipeID){
+ recipeGroup.unregister(recipeID);
+ return this;
+ }
+}
diff --git a/src/main/java/turniplabs/halplibe/helper/recipeBuilders/modifiers/FurnaceModifier.java b/src/main/java/turniplabs/halplibe/helper/recipeBuilders/modifiers/FurnaceModifier.java
new file mode 100644
index 0000000..af119b4
--- /dev/null
+++ b/src/main/java/turniplabs/halplibe/helper/recipeBuilders/modifiers/FurnaceModifier.java
@@ -0,0 +1,21 @@
+package turniplabs.halplibe.helper.recipeBuilders.modifiers;
+
+import net.minecraft.core.block.Blocks;
+import net.minecraft.core.data.registry.recipe.RecipeGroup;
+import net.minecraft.core.data.registry.recipe.RecipeSymbol;
+import net.minecraft.core.data.registry.recipe.entry.RecipeEntryFurnace;
+import turniplabs.halplibe.helper.RecipeBuilder;
+
+
+public class FurnaceModifier {
+ protected RecipeGroup recipeGroup;
+ @SuppressWarnings("unchecked")
+ public FurnaceModifier(String namespace){
+ recipeGroup = (RecipeGroup) RecipeBuilder.getRecipeGroup(namespace, "furnace", new RecipeSymbol(Blocks.FURNACE_STONE_ACTIVE.getDefaultStack()));
+ }
+ @SuppressWarnings({"unchecked", "unused"})
+ public FurnaceModifier removeRecipe(String recipeID){
+ recipeGroup.unregister(recipeID);
+ return this;
+ }
+}
diff --git a/src/main/java/turniplabs/halplibe/helper/recipeBuilders/modifiers/TrommelModifier.java b/src/main/java/turniplabs/halplibe/helper/recipeBuilders/modifiers/TrommelModifier.java
new file mode 100644
index 0000000..d9994c4
--- /dev/null
+++ b/src/main/java/turniplabs/halplibe/helper/recipeBuilders/modifiers/TrommelModifier.java
@@ -0,0 +1,115 @@
+package turniplabs.halplibe.helper.recipeBuilders.modifiers;
+
+import net.minecraft.core.WeightedRandomBag;
+import net.minecraft.core.WeightedRandomLootObject;
+import net.minecraft.core.block.Block;
+import net.minecraft.core.block.Blocks;
+import net.minecraft.core.data.registry.recipe.RecipeGroup;
+import net.minecraft.core.data.registry.recipe.RecipeSymbol;
+import net.minecraft.core.data.registry.recipe.entry.RecipeEntryTrommel;
+import net.minecraft.core.item.ItemStack;
+import turniplabs.halplibe.helper.RecipeBuilder;
+import turniplabs.halplibe.mixin.accessors.WeightedRandomBagAccessor;
+import turniplabs.halplibe.mixin.accessors.WeightedRandomBagEntryAccessor;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+public class TrommelModifier {
+ private final WeightedRandomBag trommelEntry;
+ private final String key;
+ private final String namespace;
+ @SuppressWarnings("unchecked")
+ public TrommelModifier(String namespace, String key){
+ this.key = key;
+ this.namespace = namespace;
+ trommelEntry = (WeightedRandomBag) Objects.requireNonNull(RecipeBuilder.getRecipeGroup(namespace, "trommel", new RecipeSymbol(Blocks.TROMMEL_ACTIVE.getDefaultStack())).getItem(key), "Requested recipe " + (namespace + ":trommel/" + key) + " does not exist!").getOutput();
+ }
+ @SuppressWarnings({"unchecked", "unused"})
+ public void deleteRecipe(){
+ RecipeGroup recipeGroup = (RecipeGroup) RecipeBuilder.getRecipeGroup(namespace, "trommel", new RecipeSymbol(Blocks.TROMMEL_ACTIVE.getDefaultStack()));
+ recipeGroup.unregister(key);
+ }
+ @SuppressWarnings({"unused"})
+ public TrommelModifier addEntry(WeightedRandomLootObject lootObject, double weight){
+ trommelEntry.addEntry(lootObject, weight);
+ return this;
+ }
+
+ /**
+ * @param outputStack The stack the entries to be removed use
+ * Deletes all entries matching the provided stack.
+ */
+ @SuppressWarnings({"unused"})
+ public TrommelModifier removeEntries(ItemStack outputStack){
+ List.Entry> outputs = new ArrayList<>(trommelEntry.getEntriesWithWeights());
+ for (WeightedRandomBag.Entry object : outputs){
+ WeightedRandomLootObject weightedObject = object.getObject();
+ if (weightedObject.getItemStack().isItemEqual(outputStack)){
+ ((WeightedRandomBagAccessor>)trommelEntry).getRawEntries().remove(object);
+ }
+ }
+ recalculateWeights();
+ return this;
+ }
+ /**
+ * @param outputStack The stack the entry to be removed uses
+ * Deletes the first entry matching the provided stack and weight.
+ */
+ @SuppressWarnings({"unused"})
+ public TrommelModifier removeEntry(ItemStack outputStack, double weight){
+ List.Entry> outputs = new ArrayList<>(trommelEntry.getEntriesWithWeights());
+ for (WeightedRandomBag.Entry object : outputs){
+ WeightedRandomLootObject weightedObject = object.getObject();
+ if (weightedObject.getItemStack().isItemEqual(outputStack) && weight == object.getWeight()){
+ ((WeightedRandomBagAccessor>)trommelEntry).getRawEntries().remove(object);
+ break;
+ }
+ }
+ recalculateWeights();
+ return this;
+ }
+
+ /**
+ * @param outputStack The stack the entry to be modified uses
+ * @param oldWeight The weight the bag currently uses
+ * @param newWeight The weight to replace the old weight with
+ */
+ @SuppressWarnings({"unused"})
+ public TrommelModifier setWeight(ItemStack outputStack, double oldWeight, double newWeight){
+ List.Entry> outputs = new ArrayList<>(trommelEntry.getEntriesWithWeights());
+ for (WeightedRandomBag.Entry object : outputs){
+ WeightedRandomLootObject weightedObject = object.getObject();
+ if (weightedObject.getItemStack().isItemEqual(outputStack) && oldWeight == object.getWeight()){
+ ((WeightedRandomBagEntryAccessor)object).setWeight(newWeight);
+ break;
+ }
+ }
+ recalculateWeights();
+ return this;
+ }
+ /**
+ * @param outputStack The stack the entries to be modified uses
+ * @param newWeight The weight to replace the old weight with
+ */
+ @SuppressWarnings({"unused"})
+ public TrommelModifier setWeights(ItemStack outputStack, double newWeight){
+ List.Entry> outputs = new ArrayList<>(trommelEntry.getEntriesWithWeights());
+ for (WeightedRandomBag.Entry object : outputs){
+ WeightedRandomLootObject weightedObject = object.getObject();
+ if (weightedObject.getItemStack().isItemEqual(outputStack)){
+ ((WeightedRandomBagEntryAccessor)object).setWeight(newWeight);
+ }
+ }
+ recalculateWeights();
+ return this;
+ }
+ protected void recalculateWeights(){
+ double weight = 0;
+ for (WeightedRandomBag.Entry object : trommelEntry.getEntriesWithWeights()){
+ weight += object.getWeight();
+ }
+ ((WeightedRandomBagAccessor>)trommelEntry).setAccumulatedWeight(weight);
+ }
+}
diff --git a/src/main/java/turniplabs/halplibe/helper/recipeBuilders/modifiers/WorkbenchModifier.java b/src/main/java/turniplabs/halplibe/helper/recipeBuilders/modifiers/WorkbenchModifier.java
new file mode 100644
index 0000000..ca8b920
--- /dev/null
+++ b/src/main/java/turniplabs/halplibe/helper/recipeBuilders/modifiers/WorkbenchModifier.java
@@ -0,0 +1,20 @@
+package turniplabs.halplibe.helper.recipeBuilders.modifiers;
+
+import net.minecraft.core.block.Blocks;
+import net.minecraft.core.data.registry.recipe.RecipeGroup;
+import net.minecraft.core.data.registry.recipe.RecipeSymbol;
+import net.minecraft.core.data.registry.recipe.entry.RecipeEntryCrafting;
+import turniplabs.halplibe.helper.RecipeBuilder;
+
+public class WorkbenchModifier {
+ protected RecipeGroup> recipeGroup;
+ @SuppressWarnings({"unchecked", "unused"})
+ public WorkbenchModifier(String namespace){
+ recipeGroup = (RecipeGroup>) RecipeBuilder.getRecipeGroup(namespace, "workbench", new RecipeSymbol(Blocks.WORKBENCH.getDefaultStack()));
+ }
+ @SuppressWarnings({"unchecked", "unused"})
+ public WorkbenchModifier removeRecipe(String recipeID){
+ recipeGroup.unregister(recipeID);
+ return this;
+ }
+}
diff --git a/src/main/java/turniplabs/halplibe/mixin/accessors/WeightedRandomBagAccessor.java b/src/main/java/turniplabs/halplibe/mixin/accessors/WeightedRandomBagAccessor.java
new file mode 100644
index 0000000..7978311
--- /dev/null
+++ b/src/main/java/turniplabs/halplibe/mixin/accessors/WeightedRandomBagAccessor.java
@@ -0,0 +1,17 @@
+package turniplabs.halplibe.mixin.accessors;
+
+import net.minecraft.core.WeightedRandomBag;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.gen.Accessor;
+
+import java.util.List;
+
+@Mixin(value = WeightedRandomBag.class, remap = false)
+public interface WeightedRandomBagAccessor {
+ @Accessor("entries")
+ List.Entry> getRawEntries();
+ @Accessor
+ double getAccumulatedWeight();
+ @Accessor("accumulatedWeight")
+ void setAccumulatedWeight(double weight);
+}
diff --git a/src/main/java/turniplabs/halplibe/mixin/accessors/WeightedRandomBagEntryAccessor.java b/src/main/java/turniplabs/halplibe/mixin/accessors/WeightedRandomBagEntryAccessor.java
new file mode 100644
index 0000000..58d65dd
--- /dev/null
+++ b/src/main/java/turniplabs/halplibe/mixin/accessors/WeightedRandomBagEntryAccessor.java
@@ -0,0 +1,11 @@
+package turniplabs.halplibe.mixin.accessors;
+
+import net.minecraft.core.WeightedRandomBag;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.gen.Accessor;
+
+@Mixin(value = WeightedRandomBag.Entry.class, remap = false)
+public interface WeightedRandomBagEntryAccessor {
+ @Accessor("weight")
+ void setWeight(double weight);
+}
diff --git a/src/main/resources/halplibe.mixins.json b/src/main/resources/halplibe.mixins.json
index 07f642f..3dfcfe7 100644
--- a/src/main/resources/halplibe.mixins.json
+++ b/src/main/resources/halplibe.mixins.json
@@ -12,7 +12,9 @@
"accessors.EntityFireflyFXAccessor",
"accessors.EntityFXAccessor",
"accessors.LanguageAccessor",
- "accessors.RenderManagerAccessor"
+ "accessors.RenderManagerAccessor",
+ "accessors.WeightedRandomBagAccessor",
+ "accessors.WeightedRandomBagEntryAccessor"
],
"client": [
"accessors.TileEntityRendererAccessor",