import collections, os, re, sys, zipfile
VERSIONS = ["1.20.1", "1.21.1"]
ALL = set(VERSIONS)
found = collections.defaultdict(lambda: collections.defaultdict(set))
here = os.path.dirname(os.path.abspath(__file__))
for line in open(os.path.join(here, "ids.txt")):
if line.startswith("#") or not line.strip():
continue
kind, path, versions = line.split()
if kind in ("block", "item", "entity"):
found[kind][path] = ALL if versions == "*" else set(versions.split(","))
SOUND = re.compile(rb"^[a-z0-9_]+(\.[a-z0-9_]+)+$")
for v in VERSIONS:
z = zipfile.ZipFile(os.path.expanduser(f"~/.gradle/caches/fabric-loom/{v}/minecraft-client.jar"))
names = z.namelist()
lang = __import__("json").loads(z.read("assets/minecraft/lang/en_us.json"))
for k in lang:
m = re.fullmatch(r"(effect|enchantment)\.minecraft\.([a-z0-9_]+)", k)
if m:
found[m[1]][m[2]].add(v)
for n in names:
m = re.fullmatch(r"assets/minecraft/particles/([a-z0-9_]+)\.json", n)
if m:
found["particle"][m[1]].add(v)
m = re.fullmatch(r"data/minecraft/damage_type/([a-z0-9_]+)\.json", n)
if m:
found["damage_type"][m[1]].add(v)
m = re.fullmatch(r"data/minecraft/dimension_type/([a-z0-9_]+)\.json", n)
if m:
found["dimension"][m[1]].add(v)
for n in names:
m = re.fullmatch(r"data/minecraft/tags/(blocks?|items?|entity_types?)/([a-z0-9_/]+)\.json", n)
if m:
kind = {"block": "block_tag", "blocks": "block_tag", "item": "item_tag", "items": "item_tag"}.get(m[1], "entity_tag")
found[kind][m[2]].add(v)
for n in names:
if not n.endswith(".class"):
continue
data = z.read(n)
if b"entity.firework_rocket.launch" not in data or b"block.glass.break" not in data:
continue
i = 10
count = int.from_bytes(data[8:10], "big")
idx = 1
while idx < count:
tag = data[i]
if tag == 1:
ln = int.from_bytes(data[i + 1:i + 3], "big")
s = data[i + 3:i + 3 + ln]
if SOUND.match(s) and b"." in s:
found["sound"][s.decode()].add(v)
i += 3 + ln
elif tag in (3, 4):
i += 5
elif tag in (5, 6):
i += 9
idx += 1
elif tag in (7, 8, 16, 19, 20):
i += 3
elif tag in (9, 10, 11, 12, 17, 18):
i += 5
elif tag == 15:
i += 4
else:
break
idx += 1
TITLES = {
"block": ("blocks", "Blocks."),
"item": ("items", "Items."),
"entity": ("entities", "Entity types."),
"effect": ("effects", "Mob effects."),
"enchantment": ("enchantments", "Enchantments."),
"particle": ("particles", "Particle types."),
"damage_type": ("damage_types", "Damage types."),
"dimension": ("dimensions", "Dimensions (and their types)."),
"sound": ("sounds", "Sound events."),
"block_tag": ("block_tags", "Block tags (`tag` paths)."),
"item_tag": ("item_tags", "Item tags."),
"entity_tag": ("entity_tags", "Entity type tags."),
}
KEYWORDS = {"as", "break", "const", "continue", "crate", "else", "enum", "extern", "false", "fn", "for", "if", "impl", "in",
"let", "loop", "match", "mod", "move", "mut", "pub", "ref", "return", "self", "static", "struct", "super", "trait",
"true", "type", "unsafe", "use", "where", "while", "async", "await", "dyn"}
out = ["//! The game's ids as constants (`yog_api::vanilla::items::REDSTONE`), so",
"//! mod code names them instead of spelling them. Generated by",
"//! `yog-cli/src/vanilla/gen_api.py` from the supported versions",
f"//! ({', '.join(VERSIONS)}); one missing from a version says so.",
"//! Don't edit — rerun the script.",
""]
for kind, (module, doc) in TITLES.items():
out.append(f"/// {doc}")
out.append(f"pub mod {module} {{")
used = set()
for path in sorted(found[kind]):
ident = re.sub(r"[^A-Za-z0-9]", "_", path).upper()
if ident[0].isdigit():
ident = "_" + ident
if ident.lower() in KEYWORDS:
ident += "_"
while ident in used:
ident += "_"
used.add(ident)
vs = found[kind][path]
if vs != ALL:
out.append(f" /// Only in {', '.join(sorted(vs))}.")
out.append(f' pub const {ident}: &str = "minecraft:{path}";')
out.append("}")
out.append("")
open(sys.argv[1], "w").write("\n".join(out))
print({k: len(v) for k, v in found.items()})