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