Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
6d029d9
Switch to json for serialisation
40476 Feb 8, 2026
8329d43
Update installer to use new recipes
40476 Feb 8, 2026
ab36c0b
Fix json library import and add error handling for JSON decoding
40476 Feb 8, 2026
1f6fabb
Add server.jar to .gitignore and fix syntax errors in crafting.lua
40476 Feb 8, 2026
34fc565
fix: update json module paths and add missing requires
40476 Feb 8, 2026
b232c31
refactor(crafting): remove unused bfile struct definitions
40476 Feb 8, 2026
91457f7
ooga
40476 Feb 8, 2026
faa495c
HEY IT MOSTLY WORKS JUST GOTTA ADD ALIASES FOR ITEM TAGS
40476 Feb 8, 2026
ceeebcf
refactor: reorganize and enhance item tag processing in gen_bins.py
40476 Feb 8, 2026
7aecc03
chore(crafting): refactor tag resolution and add furnace recipe loading
40476 Feb 8, 2026
aaccf44
fix: improve item selection and grid recipe handling
40476 Feb 8, 2026
fef83b0
fix(crafting): resolve crafting chain simulation and job management i…
40476 Feb 8, 2026
29133fc
fix: resolve tag references in crafting selection and grid recipe han…
40476 Feb 8, 2026
d8c02da
refactor: rename gen_bins.py to gen_recipes.py and add auto-download …
40476 Feb 8, 2026
f94ad2d
refactor: optimize disposal module with safer item handling and error…
40476 Feb 8, 2026
8019ccf
refactor: Remove bfile.lua dependency and improve disposal module rel…
40476 Feb 8, 2026
5842bcb
fix: Workaround for inventory pushItems bug with 64-item stacks
40476 Feb 8, 2026
e93f005
fix: Stop installer script after child execution and remove unused fu…
40476 Feb 8, 2026
5e39870
fix(inventory): fix item transfer for 16 and 64 stack sizes
40476 Feb 8, 2026
28ab277
was fixing the wrong thing lol
40476 Feb 8, 2026
e82bcaf
Merge branch 'Storehaus:master' into master
40476 Feb 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
249 changes: 249 additions & 0 deletions .github/scripts/gen_recipes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
import zipfile
import json
import struct
import os
import io
import urllib.request

def download_latest_server():
print("Locating latest Minecraft server jar...")
manifest_url = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json"

try:
# 1. Fetch version manifest to find the latest version ID
with urllib.request.urlopen(manifest_url) as response:
manifest = json.loads(response.read().decode('utf-8'))

latest_version = manifest['latest']['release']
print(f"Latest release version: {latest_version}")

# 2. Find the URL for the specific version package JSON
version_url = None
for version in manifest['versions']:
if version['id'] == latest_version:
version_url = version['url']
break

if not version_url:
print("Error: Could not find version details.")
return False

# 3. Fetch version details to get the actual download link
with urllib.request.urlopen(version_url) as response:
version_data = json.loads(response.read().decode('utf-8'))

server_url = version_data['downloads']['server']['url']
print(f"Downloading server.jar from {server_url}...")

# 4. Download the file
urllib.request.urlretrieve(server_url, "server.jar")
print("Download complete!")
return True

except Exception as e:
print(f"Failed to download server.jar: {e}")
return False

def process_tags(zip_obj, tags_map):
# Scan for item tags
# Path format in jar: data/<namespace>/tags/item/<name>.json
tag_files = [f for f in zip_obj.namelist() if '/tags/item/' in f and f.endswith('.json')]

# First pass: Load all raw tags
raw_tags = {}

for file_path in tag_files:
try:
with zip_obj.open(file_path) as file:
data = json.load(file)

# Derive tag name from path
# data/minecraft/tags/item/logs.json -> minecraft:logs
parts = file_path.split('/')
# parts usually: ['data', 'minecraft', 'tags', 'item', 'logs.json']
if len(parts) >= 5:
namespace = parts[1]
name = os.path.splitext(parts[-1])[0]
tag_key = f"{namespace}:{name}" # e.g. "minecraft:logs"
# Add # prefix to match how recipe inputs look
full_key = f"#{tag_key}"

values = []
raw_values = data.get("values", [])
for v in raw_values:
if isinstance(v, str):
values.append(v)
elif isinstance(v, dict) and "id" in v:
values.append(v["id"])

raw_tags[full_key] = values
except Exception:
continue

# Second pass: Resolve tags within tags (basic flattening)
# We loop a few times to resolve nested tags like #minecraft:logs containing #minecraft:oak_logs
for _ in range(3):
for tag, values in raw_tags.items():
new_values = []
for v in values:
if v.startswith('#'):
# It's a reference to another tag, expand it if we know it
if v in raw_tags:
new_values.extend(raw_tags[v])
else:
new_values.append(v) # Keep it if we can't resolve it
else:
new_values.append(v)
# Remove duplicates
raw_tags[tag] = list(set(new_values))

# Copy to the output map
for k, v in raw_tags.items():
# Remove the # prefix for the key in the aliases table if preferred,
# but keeping it makes lookup easier for exact matches on inputs like "#minecraft:logs"
tags_map[k] = v

return len(raw_tags)

def process_recipes(zip_obj, furnace_list, crafting_set, crafting_recipes):
# 1.21 changed folder from 'recipes' to 'recipe'
recipe_files = [f for f in zip_obj.namelist() if f.startswith('data/minecraft/recipe/') and f.endswith('.json')]
count = 0
for file_path in recipe_files:
try:
with zip_obj.open(file_path) as file:
data = json.load(file)
rtype = data.get("type", "")

# --- Furnace / Smelting Logic ---
if rtype in ["minecraft:smelting", "minecraft:blasting"]:
ing = data.get("ingredient")
# Handle 1.21 list or single string/dict
if isinstance(ing, list): ing = ing[0]
item_in = ing if isinstance(ing, str) else ing.get("item") or ing.get("tag")
# Ensure tags start with #
if item_in and not item_in.startswith('#') and ':' in item_in and not item_in.startswith('minecraft:'):
# Heuristic: if it's a tag in the json but just a string here, we might miss the #
# But standard JSON reader usually sees "tag": "minecraft:logs"
pass

if isinstance(ing, dict) and "tag" in ing:
item_in = "#" + ing["tag"]

# 1.21 result uses 'id' instead of 'item'
res = data.get("result")
item_out = res if isinstance(res, str) else res.get("id") or res.get("item")

if item_in and item_out:
furnace_list.append((item_in, item_out))

# --- Crafting Logic (Grid / Crafting) ---
if "crafting" in rtype:
res = data.get("result", {})
# 1.21 result uses 'id' instead of 'item'
out = res if isinstance(res, str) else res.get("id") or res.get("item")
if out:
crafting_set.add(out)

# Process crafting recipes
if rtype in ["minecraft:crafting_shaped", "minecraft:crafting_shapeless"]:
recipe = {
"type": rtype,
"result": {
"item": out,
"count": data.get("result", {}).get("count", 1)
}
}

if rtype == "minecraft:crafting_shaped":
recipe["pattern"] = data.get("pattern", [])
recipe["key"] = data.get("key", {})
else:
recipe["ingredients"] = data.get("ingredients", [])

crafting_recipes.append(recipe)
count += 1
except Exception:
continue
return count

def generate_bins():
# Automatically download the latest server jar
if not download_latest_server():
print("Aborting generation due to download failure.")
return

jar_path = "server.jar"
furnace_data = []
crafting_items = set()
crafting_recipes = []
tags_map = {}

if not os.path.exists(jar_path):
print("server.jar not found.")
return

with zipfile.ZipFile(jar_path, 'r') as outer_zip:
# Check for nested Bundler JAR first (standard for 1.21)
is_bundler = False
for name in outer_zip.namelist():
if name.startswith("META-INF/versions/") and name.endswith(".jar"):
print(f"Detected Bundler. Processing inner JAR: {name}")
with outer_zip.open(name) as inner_file:
inner_data = io.BytesIO(inner_file.read())
with zipfile.ZipFile(inner_data) as inner_zip:
process_recipes(inner_zip, furnace_data, crafting_items, crafting_recipes)
process_tags(inner_zip, tags_map)
is_bundler = True
break

# If not a bundler, try the root
if not is_bundler:
process_recipes(outer_zip, furnace_data, crafting_items, crafting_recipes)
process_tags(outer_zip, tags_map)

# Create unified JSON structure
recipes = {
"recipes": {
"furnace": [],
"crafting": []
},
"itemLookup": {},
"aliases": tags_map # Add the aliases/tags table here
}

# Add furnace recipes
for item_in, item_out in furnace_data:
recipes["recipes"]["furnace"].append({
"type": "minecraft:smelting",
"ingredient": item_in,
"result": item_out,
"experience": 0.7,
"cookingtime": 200
})

# Add crafting recipes
for recipe in crafting_recipes:
recipes["recipes"]["crafting"].append(recipe)

# Add item lookup
item_index = 1
for item in sorted(list(crafting_items)):
recipes["itemLookup"][item] = item_index
item_index += 1

# Write to JSON file
if not os.path.exists("recipes"):
os.makedirs("recipes")

with open("recipes/recipes.json", "w") as f:
json.dump(recipes, f, indent=2)

print(f"Success! Generated JSON:")
print(f" - {len(tags_map)} tags/aliases processed")
print(f" - {len(furnace_data)} furnace recipes")
print(f" - {len(crafting_recipes)} crafting recipes")
print(f" - {len(crafting_items)} items")

if __name__ == "__main__":
generate_bins()
34 changes: 34 additions & 0 deletions .github/workflows/gen_recipes.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Generate Recipes

on:
workflow_dispatch:

jobs:
generate:
runs-on: ubuntu-latest
permissions:
contents: write

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'

- name: Run Recipe Generator Script
run: python .github/scripts/gen_recipes.py

- name: Commit and Push changes
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git add recipes/recipes.json
if git diff --staged --quiet; then
echo "No changes to recipes.json detected."
else
git commit -m "Auto-update recipes.json from latest server jar"
git push
fi
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ log.txt
config.lua
basalt.lua
stone.json
server.jar

# Jekyll junk files
.jekyll-metadata
.jekyll-cache/
_site/

Loading