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