commit 9f948790727bcd298250571c915a88670eb2dd8e Author: foreverpyrite <51493121+ForeverPyrite@users.noreply.github.com> Date: Sun Nov 9 15:44:11 2025 -0600 Inital commit. Publishing this to GitHub cause I've forgotten to for so long. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..003d32e --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +data/* +server*/* +*.jar +*.zip diff --git a/README.md b/README.md new file mode 100644 index 0000000..744e354 --- /dev/null +++ b/README.md @@ -0,0 +1,49 @@ +# Blast Furnace Extended Datapack + +This minor QoL datapack extends the functionality of the Blast Furnace by adding all possible smelting recipes (recipes that aren't in the Smoker) to it. + +## How to Update This Datapack for a New Minecraft Version + +This datapack can be automatically updated to include all the latest recipes from any new version of Minecraft. + +### Prerequisites + +* You must have Python 3 installed on your system. +* You need to download the official Minecraft Server `.jar` file for the version you want to support. + +### Update Steps + +1. **Download the Minecraft Server `.jar`** + * You can find the official server file on the [Minecraft website](https://www.minecraft.net/en-us/download/server). + * Once downloaded, place the file in this directory and rename it to `server.jar`. + +2. **Run the Generation Script** + * Open a terminal or command prompt in this directory. + * Run the Python script with the following command: + ```bash + python generate_recipes.py + ``` + * The script will automatically: + * Delete any old recipes in the `data/minecraft/recipes/` directory. + * Read all the "smelting" and "blasting" recipes from the `server.jar`. + * Create new "blasting" recipes for any that don't already have one. + * Save all the new recipes into the `data/minecraft/recipes/` directory. + +3. **Update `pack.mcmeta`** + * Open the `pack.mcmeta` file. + * Find the `pack_format` number and update it to match the new version of Minecraft. You can find a list of `pack_format` versions on the [Minecraft Wiki](https://minecraft.wiki/Pack_format). + * For example, for Minecraft 1.21, the `pack_format` is `32`. + +4. **Create the Datapack `.zip` file** + * You are now ready to distribute the datapack. + * Create a `.zip` file that contains the `data` directory and the `pack.mcmeta` file. + * This `.zip` file is what you will place in the `datapacks` folder of your Minecraft world. + +This process *should* work until there are major changes to how Minecraft manages recipes, datapacks, ect. + + +## TODO +- [ ] Ensure everything works as expected on other devices +- [ ] Perhaps create a Dockerfile, if there is a "latest-release" style download link +- [ ] Publish on modpack sites (if there are no equivalents) +- [ ] Maybe some better inline documentation could be cool. diff --git a/generate_recipes.py b/generate_recipes.py new file mode 100644 index 0000000..74d0642 --- /dev/null +++ b/generate_recipes.py @@ -0,0 +1,141 @@ +import json +from os.path import basename +import zipfile +import shutil +from pathlib import Path + +# --- Configuration --- +# The path to the Minecraft server .jar file. +# You'll need to download this for the version you want to target. +# You can usually find it on the official Minecraft website. +JAR_PATH = Path("server.jar") + +# The directory where the generated datapack files will be placed. +DATAPACK_DIR = Path("data") + + +# --- Script --- +def get_recipes_from_jar( + jar_path: Path, recipe_type: str = "smelting" +) -> dict[str, dict[str, str]]: + """Extracts all recipes of a specific type from the Minecraft server jar.""" + recipes: dict[str, dict[str, str]] = {} + with zipfile.ZipFile(jar_path, "r") as jar: + for filename in jar.namelist(): + if filename.startswith("data/minecraft/recipe/") and filename.endswith( + ".json" + ): + with jar.open(filename) as recipe_file: + try: + recipe_data = json.load(recipe_file) + if recipe_data.get("type") == f"minecraft:{recipe_type}": + # Use the filename (without extension) as the recipe ID + recipe_id = Path(filename).stem + recipes[recipe_id] = recipe_data + except (json.JSONDecodeError, UnicodeDecodeError): + # This handles cases where a file isn't valid JSON, which can happen. + print(f"Warning: Could not parse {filename}, skipping.") + continue + return recipes + + +def convert_to_blasting(smelting_recipe: dict[str, str | int]) -> dict[str, str | int]: + """Converts a smelting recipe to a blasting recipe.""" + blasting_recipe = smelting_recipe.copy() + blasting_recipe["type"] = "minecraft:blasting" + # Blasting is twice as fast as smelting + # gotta type check it to get pyright to shut up... + if "cookingtime" in blasting_recipe and isinstance( + blasting_recipe["cookingtime"], int + ): + blasting_recipe["cookingtime"] = int(blasting_recipe["cookingtime"] / 2) + else: + # Default smelting time is 200 ticks + blasting_recipe["cookingtime"] = 100 + return blasting_recipe + + +def get_server_jar_from_jar(jar_path: Path) -> Path | None: + with zipfile.ZipFile(jar_path, "r") as jar: + for filename in jar.namelist(): + if filename.startswith("META-INF/versions/") and filename.endswith(".jar"): + jar.extract(filename, basename(filename)) + return Path(basename(filename) + "/" + filename) + return None + + +def main(): + """Main function to generate the blasting recipes.""" + print("Starting recipe generation...") + + if not Path(JAR_PATH).exists(): + print("Error: The server file was not found.") + print("Please download the Minecraft server .jar for the version you want,") + print( + f"rename it to '{JAR_PATH}', and place it in the same directory as this script." + ) + return + + server_jar_path = get_server_jar_from_jar(JAR_PATH) + if server_jar_path is None: + print( + "Error: Unable to pull server jar out of server jar. (yes, you read that right)" + ) + print("Are you sure you downloaded a proper server jar?") + return + + print("Loading recipes from the server jar...") + smelting_recipes = get_recipes_from_jar(server_jar_path, "smelting") + blasting_recipes = get_recipes_from_jar(server_jar_path, "blasting") + smoking_recipes = get_recipes_from_jar(server_jar_path, "smoking") + print(f"Found {len(smelting_recipes)} smelting recipes.") + print(f"Found {len(blasting_recipes)} vanilla blasting recipes.") + print(f"Found {len(smoking_recipes)} vanilla smoking recipes.") + + # Determine which smelting recipes need a blasting equivalent + # We do this by comparing the output item of the recipes + blasting_results = [recipe.get("result") for recipe in blasting_recipes.values()] + # Exclude recipes that are already handled by the smoker. + smoking_results = [recipe.get("result") for recipe in smoking_recipes.values()] + + # Holy list comprehension + recipes_to_create = { + name: recipe + for name, recipe in smelting_recipes.items() + if recipe.get("result") not in blasting_results + and recipe.get("result") not in smoking_results + } + + print( + f"\nFound {len(recipes_to_create)} smelting recipes to convert to blasting (after excluding existing blasting/smoking recipes)." + ) + + # Clean up old recipes and create the directory structure + # Using 'recipe' as the path, per your correction. + recipes_path = DATAPACK_DIR / "minecraft" / "recipe" + if recipes_path.exists(): + shutil.rmtree(recipes_path) + recipes_path.mkdir(parents=True, exist_ok=True) + print(f"Cleaned and created directory: {recipes_path}") + + # Generate the new .json files + for name, recipe in recipes_to_create.items(): + blasting_recipe = convert_to_blasting(recipe) + # To avoid any potential conflicts, we'll add a suffix to the name + output_filename = recipes_path / f"{name}_from_blasting.json" + with open(output_filename, "w") as f: + json.dump(blasting_recipe, f, indent=2) + + print(f"\nSuccessfully generated {len(recipes_to_create)} new blasting recipes!") + print(f"They have been saved in: {recipes_path}") + print("\nNext steps:") + print( + "1. Make sure your `pack.mcmeta` file has the correct `pack_format` for the Minecraft version you are targeting." + ) + print( + "2. Zip the `data` directory and the `pack.mcmeta` file to create your distributable datapack." + ) + + +if __name__ == "__main__": + main() diff --git a/pack.mcmeta b/pack.mcmeta new file mode 100644 index 0000000..45d7d2e --- /dev/null +++ b/pack.mcmeta @@ -0,0 +1,7 @@ +{ + "pack": { + "description": "Adds all smelting recipes to the Blast Furnace for 1.21.10", + "min_format": 88, + "max_format": 99 + } +}