Public Access
107 lines
4.6 KiB
Python
107 lines
4.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Rebuild the bundled 1.21.1 structure template from Mystcraft Legacy's layout.
|
|
|
|
Run manually; world generation never downloads anything. The pinned Legacy source
|
|
is LGPL-3.0 and its original block placement order is preserved here.
|
|
"""
|
|
|
|
import gzip
|
|
import hashlib
|
|
import re
|
|
import struct
|
|
import urllib.request
|
|
from pathlib import Path
|
|
|
|
SOURCE = ("https://raw.githubusercontent.com/Mystcraft/Mystcraft-Legacy/"
|
|
"9bc8ddc061845df0cd5ea47f4fdef773cff786ae/src/main/java/"
|
|
"com/xcompwiz/mystcraft/world/gen/structure/ComponentScatteredFeatureSmallLibrary.java")
|
|
SHA256 = "487b3bef48dacfbea35ac2020dbb5543dab0c2a9d99bae4a11acb5cc6fc8bfac"
|
|
OUTPUT = Path(__file__).resolve().parents[1] / "src/main/resources/data/rivencraft/structure/mystcraft_library.nbt"
|
|
LECTERNS = {(6, 2), (8, 4), (8, 5), (8, 6), (6, 8)}
|
|
BLOCKS = {
|
|
"AIR": "air", "BOOKSHELF": "bookshelf", "COBBLESTONE": "cobblestone",
|
|
"COBBLESTONE_WALL": "cobblestone_wall", "PLANKS": "oak_planks",
|
|
"STONE_STAIRS": "stone_stairs", "STONE_SLAB": "cobblestone_slab", "WEB": "cobweb",
|
|
}
|
|
PATTERN = re.compile(r"setBlockState\(worldObj, Blocks\.(\w+)\.(.+?), "
|
|
r"(\d+), (\d+), (\d+), boundingbox\);")
|
|
|
|
|
|
def nbt_string(value):
|
|
encoded = value.encode("utf-8")
|
|
return struct.pack(">H", len(encoded)) + encoded
|
|
|
|
|
|
def tag(kind, name, payload):
|
|
return bytes([kind]) + nbt_string(name) + payload
|
|
|
|
|
|
def compound(entries):
|
|
return b"".join(entries) + b"\0"
|
|
|
|
|
|
def list_tag(name, element_kind, elements):
|
|
return tag(9, name, bytes([element_kind]) + struct.pack(">i", len(elements)) + b"".join(elements))
|
|
|
|
|
|
def int_list(name, values):
|
|
return list_tag(name, 3, [struct.pack(">i", value) for value in values])
|
|
|
|
|
|
def main():
|
|
source = urllib.request.urlopen(SOURCE, timeout=15).read()
|
|
if hashlib.sha256(source).hexdigest() != SHA256:
|
|
raise ValueError("Pinned Legacy source changed")
|
|
placements = {}
|
|
matches = PATTERN.findall(source.decode("utf-8"))
|
|
if len(matches) != 694:
|
|
raise ValueError(f"Expected 694 Legacy block placements, got {len(matches)}")
|
|
for legacy_block, state, x, y, z in matches:
|
|
x, y, z = int(x), int(y), int(z)
|
|
if legacy_block not in BLOCKS or not (0 <= x <= 10 and 0 <= y <= 10 and 0 <= z <= 10):
|
|
raise ValueError(f"Unsupported Legacy block/position: {legacy_block} {(x, y, z)}")
|
|
properties = {}
|
|
facing = re.search(r"BlockStairs.FACING, EnumFacing.(\w+)", state)
|
|
if facing:
|
|
properties["facing"] = facing.group(1).lower()
|
|
if "BlockStairs.EnumHalf.TOP" in state:
|
|
properties["half"] = "top"
|
|
if legacy_block == "STONE_SLAB":
|
|
meta = re.search(r"getStateFromMeta\((\d+)\)", state)
|
|
if not meta or int(meta.group(1)) not in (3, 11):
|
|
raise ValueError(f"Unknown Legacy slab metadata: {state}")
|
|
properties["type"] = "top" if int(meta.group(1)) == 11 else "bottom"
|
|
placements[x, y, z] = ("minecraft:" + BLOCKS[legacy_block], tuple(sorted(properties.items())))
|
|
|
|
# Legacy's lower lectern is a bookshelf at y=1. Remove that and its
|
|
# two-block upper half; runtime places a one-block Rivencraft stand at y=1.
|
|
for x, z in LECTERNS:
|
|
placements.pop((x, 1, z), None)
|
|
placements.pop((x, 2, z), None)
|
|
# Chest and its inventory are assigned at runtime, never frozen in NBT.
|
|
placements.pop((4, 1, 2), None)
|
|
placements = {pos: state for pos, state in placements.items() if state[0] != "minecraft:air"}
|
|
|
|
palette = sorted(set(placements.values()))
|
|
indexes = {state: index for index, state in enumerate(palette)}
|
|
palette_tags = []
|
|
for name, properties in palette:
|
|
entries = [tag(8, "Name", nbt_string(name))]
|
|
if properties:
|
|
entries.append(tag(10, "Properties", compound(
|
|
[tag(8, key, nbt_string(value)) for key, value in properties])))
|
|
palette_tags.append(compound(entries))
|
|
block_tags = [compound([int_list("pos", (x, y, z)),
|
|
tag(3, "state", struct.pack(">i", indexes[state]))])
|
|
for (x, y, z), state in sorted(placements.items())]
|
|
root = compound([int_list("size", (11, 11, 11)), list_tag("palette", 10, palette_tags),
|
|
list_tag("blocks", 10, block_tags), list_tag("entities", 10, []),
|
|
tag(3, "DataVersion", struct.pack(">i", 3955))])
|
|
OUTPUT.parent.mkdir(parents=True, exist_ok=True)
|
|
OUTPUT.write_bytes(gzip.compress(tag(10, "", root), mtime=0))
|
|
print(f"Wrote {OUTPUT}: {len(placements)} blocks, {len(palette)} palette states")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|