mirror of
https://github.com/ForeverPyrite/blast-furnace-extended.git
synced 2025-12-10 01:38:07 +00:00
Inital commit.
Publishing this to GitHub cause I've forgotten to for so long.
This commit is contained in:
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
data/*
|
||||||
|
server*/*
|
||||||
|
*.jar
|
||||||
|
*.zip
|
||||||
49
README.md
Normal file
49
README.md
Normal 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
141
generate_recipes.py
Normal 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
7
pack.mcmeta
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user