Inital commit.

Publishing this to GitHub cause I've forgotten to for so long.
This commit is contained in:
foreverpyrite
2025-11-09 15:44:11 -06:00
commit 9f94879072
4 changed files with 201 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
data/*
server*/*
*.jar
*.zip

49
README.md Normal file
View File

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

141
generate_recipes.py Normal file
View File

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

7
pack.mcmeta Normal file
View File

@@ -0,0 +1,7 @@
{
"pack": {
"description": "Adds all smelting recipes to the Blast Furnace for 1.21.10",
"min_format": 88,
"max_format": 99
}
}