Skip to content

Instantly share code, notes, and snippets.

@zlrc
Last active December 19, 2025 04:46
Show Gist options
  • Select an option

  • Save zlrc/f7da49503ea7767b5814dfd3b0c7b66f to your computer and use it in GitHub Desktop.

Select an option

Save zlrc/f7da49503ea7767b5814dfd3b0c7b66f to your computer and use it in GitHub Desktop.
Old export script for my Minecraft data pack, "Advance Your Health!". Generates overrides for every advancement in the game to run a function each time the player earns an advancement. This is single-purpose, but shouldn't be hard to modify into a more general script (see function: "create_advancement_override").
# Datapack Exporter by https://github.com/zlrc
# Version: 1
# License: MIT
# Derived from onnowhere's advancements generator script, licensed under CC0 for this specific version.
# source: https://github.com/onnowhere/advancement-disabler/blob/fb50ae71ac454731c312fff2e6bf3d650c732d26/advancements_generator.py
# license: https://github.com/onnowhere/advancement-disabler/blob/fb50ae71ac454731c312fff2e6bf3d650c732d26/LICENSE.md
import os
from shutil import rmtree, copy, copytree
from zipfile import ZipFile
import json
import sys
import errno
def create_path(filepath : str):
"""Generates a directory at the given path, alias for os.makedirs but with race condition guarding."""
if not os.path.exists(filepath):
try:
os.makedirs(filepath)
except OSError as exc: # Guard against race condition
if exc.errno != errno.EEXIST:
raise
def create_file(filename : str, contents : str):
"""Generates a file with the given path and contents while avoiding race conditions."""
create_path(os.path.dirname(filename))
with open(filename, 'w', encoding='utf-8') as f:
f.writelines(contents)
def create_advancement_override(source_data : dict, out_filename : str):
"""
Creates a copy of the provided advancement data with a custom reward function appended to it.
This override copy is immediately written to a new file.
"""
data = source_data
if not "rewards" in data:
data["rewards"] = {}
if "function" in data["rewards"]:
existing_fn = data["rewards"]["function"]
print(f"'{os.path.basename(out_filename)}' already has a function defined: {existing_fn}")
return
data["rewards"]["function"] = "advance_your_health:increment_score"
# Write to file
create_file(out_filename, json.dumps(data, separators=(',', ':')))
def parse_mcmeta(path : str) -> object:
"""Reads and returns JSON data from the pack.mcmeta at the provided path."""
data = {}
with open(path, "r") as mcmeta:
data = json.load(mcmeta)
if not "title" in data:
data["title"] = "Untitled Datapack"
if not "version" in data:
data["version"] = ""
return data
def zip_files(dirname : str, zipname : str):
"""Generates a zip archive of the provided directory (dirname)."""
path_length = len(dirname)
with ZipFile(zipname, "w") as archive:
# Iterate over all the files in directory
for folderName, subfolders, filenames in os.walk(dirname):
zipFolderName = folderName[path_length:]
for filename in filenames:
# Create complete filepath of file in directory
filePath = os.path.join(folderName, filename)
zipPath = os.path.join(zipFolderName, filename)
# Add file to zip
archive.write(filePath, zipPath)
def generate_advancements(jar_file : str):
"""Generates a datapack with all advancements from the jar file overridden."""
jar_file = jar_file.strip('"')
# Verify jar file
if os.path.splitext(jar_file)[1] != ".jar":
raise ValueError("Error: Invalid file type. Expected '.jar'.")
# Create Directories
mc_version = os.path.splitext(os.path.basename(jar_file))[0]
mcmeta_path = "pack.mcmeta"
pack_meta = parse_mcmeta(mcmeta_path)
data_dir = "data"
dist_dir = f"_dist/{pack_meta['title']}{' v' if pack_meta['version'] else ''}{pack_meta['version']} (MC {mc_version})"
dist_zip = f"{dist_dir}.zip"
advancements_dir = "data/minecraft/advancement"
# Reset existing export directory
if os.path.exists(dist_dir):
rmtree(dist_dir)
create_path(dist_dir)
copy("pack.png", dist_dir)
copy(mcmeta_path, dist_dir)
copytree(data_dir, f"{dist_dir}/data")
# Generate advancements
with ZipFile(jar_file, "r") as archive:
for file in archive.namelist():
if file.startswith(advancements_dir) and os.path.splitext(file)[1] == ".json" and not "recipes" in os.path.dirname(file):
# Decode and modify advancement data
with archive.open(file, "r") as json_file:
data : dict = json.loads(json_file.read().decode("utf-8"))
out_path : str = os.path.join(dist_dir, *file.split("/"))
create_advancement_override(data, out_path)
# Delete old zip
if os.path.exists(dist_zip):
os.remove(dist_zip)
# Zip datapack files
zip_files(dist_dir, dist_zip)
# Cleanup: remove the unzipped directory
rmtree(dist_dir)
if __name__ == "__main__":
try:
jar_file = sys.argv[1]
try:
print("Generating advancements...")
generate_advancements(jar_file)
input("Finished generating. Press 'enter' or close this window to finish.")
except:
print("Stopped.")
except:
print("Welcome to the Datapack Exporter")
print("NOTE: This will export your datapack with " +
"\nseveral overwrite files for the vanilla advancements, " +
"\nit is not ready for general-purpose exports yet.")
while True:
try:
print("----------------------------------------------")
print("Minecraft jar files can be found in '.minecraft/versions'.")
jar_file = input("Drop Minecraft jar file here and hit enter: ")
print("Generating advancements...")
generate_advancements(jar_file)
input("Finished generating. Press 'enter' to generate again or close this window to finish.")
except KeyboardInterrupt as e:
print(e)
break
except EOFError as e:
print(e)
break
except Exception as e:
print(e)
print("Error encountered while generating. Please try again.")
print("Shutting down...")
sys.exit()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment