Files
minecraft-builder-mcp/scripts/builds/gothic_hall/scene.py
T

158 lines
6.7 KiB
Python

"""Deterministic voxel blueprint for the generated Gothic hall reference."""
from collections import Counter, defaultdict
import importlib
import re
ORIGIN = (-50, -60, -40)
class Voxels:
def __init__(self):
self.blocks = {}
self.stage = 'site'
self.owners = {}
def set(self, x, y, z, block):
assert all(isinstance(v, int) for v in (x, y, z)), (x, y, z)
assert -2 <= x <= 68 and -1 <= y <= 64 and -2 <= z <= 68, (x, y, z)
if not block.startswith('minecraft:'):
block = 'minecraft:' + block
assert re.fullmatch(r'minecraft:[a-z0-9_]+(?:\[[a-z0-9_=,]+\])?', block), block
if block == 'minecraft:air':
self.blocks.pop((x, y, z), None)
self.owners.pop((x, y, z), None)
else:
self.blocks[(x, y, z)] = block
self.owners[(x, y, z)] = self.stage
def box(self, x1, y1, z1, x2, y2, z2, block):
x1, x2 = sorted((x1, x2))
y1, y2 = sorted((y1, y2))
z1, z2 = sorted((z1, z2))
for y in range(y1, y2 + 1):
for z in range(z1, z2 + 1):
for x in range(x1, x2 + 1):
self.set(x, y, z, block)
def clear(self, x1, y1, z1, x2, y2, z2):
self.box(x1, y1, z1, x2, y2, z2, 'air')
def site(v):
v.box(1, 0, 1, 63, 0, 62, 'smooth_stone')
for z in (1, 62):
v.box(1, 0, z, 63, 0, z, 'polished_andesite')
for x in (1, 63):
v.box(x, 0, 1, x, 0, 62, 'polished_andesite')
# A few continuous paving bands give the terrace scale without noisy texture.
for x in (5, 33, 60):
v.box(x, 0, 2, x, 0, 61, 'stone_bricks')
for z in (4, 59):
v.box(2, 0, z, 62, 0, z, 'stone_bricks')
for step in range(1, 7):
z = step + 1
v.box(13, 1, z, 25, step, z, 'stone_bricks')
v.box(14, step, z, 24, step, z,
'stone_brick_stairs[facing=south,half=bottom,shape=straight,waterlogged=false]')
for x in (12, 26):
v.box(x, 1, z, x, step + 1, z, 'polished_andesite')
v.set(x, step + 2, z, 'stone_brick_slab[type=bottom,waterlogged=false]')
v.box(13, 1, 8, 25, 6, 9, 'stone_bricks')
for x in (8, 30, 35, 61):
for z in (6, 59):
v.box(x, 1, z, x + 1, 1, z + 1, 'polished_andesite')
v.box(x, 2, z, x + 1, 3, z + 1, 'stone_bricks')
v.box(x, 4, z, x + 1, 4, z + 1,
'stone_brick_slab[type=bottom,waterlogged=false]')
def connection(v):
# Two-level enclosed passage between the great hall and companion wing.
v.stage = 'connecting gallery'
v.box(31, 0, 34, 40, 6, 42, 'stone_bricks')
v.box(31, 7, 34, 40, 13, 34, 'stone_bricks')
v.box(31, 7, 42, 40, 13, 42, 'stone_bricks')
v.box(31, 13, 33, 40, 13, 43, 'polished_andesite')
v.box(31, 14, 34, 40, 14, 42, 'deepslate_tiles')
v.box(31, 15, 36, 40, 15, 40, 'deepslate_tiles')
v.box(31, 16, 38, 40, 16, 38, 'deepslate_tiles')
for z, facing, y in [(34, 'south', 14), (35, 'south', 14),
(36, 'south', 15), (37, 'south', 15),
(39, 'north', 15), (40, 'north', 15),
(41, 'north', 14), (42, 'north', 14)]:
v.box(31, y, z, 40, y, z,
f'deepslate_tile_stairs[facing={facing},half=bottom,shape=straight,waterlogged=false]')
v.clear(30, 7, 36, 32, 11, 39)
v.clear(33, 8, 36, 34, 11, 39)
v.clear(35, 9, 36, 40, 12, 39)
v.box(31, 6, 36, 32, 6, 39, 'spruce_planks')
for x, y in [(33, 6), (34, 7)]:
v.box(x, y, 36, x, y, 39,
'spruce_stairs[facing=east,half=bottom,shape=straight,waterlogged=false]')
v.box(35, 8, 36, 40, 8, 39, 'spruce_planks')
def build_scene():
v = Voxels()
site(v)
for name, stage in [('main_hall', 'great hall'), ('side_wing', 'side wing'), ('tower', 'bell tower')]:
v.stage = stage
importlib.import_module('builds.gothic_hall.' + name).build(v)
connection(v)
return v
def boxes(blocks):
"""Merge runs on X, then Z and Y, preserving the exact sparse block mask."""
rows = defaultdict(list)
for (x, y, z), block in blocks.items():
rows[(y, z, block)].append(x)
runs = []
for (y, z, block), xs in sorted(rows.items()):
xs.sort(); start = end = xs[0]
for x in xs[1:]:
if x == end + 1:
end = x
else:
runs.append((start, y, z, end, y, z, block)); start = end = x
runs.append((start, y, z, end, y, z, block))
merged = []
groups = defaultdict(list)
for x1, y1, z1, x2, y2, z2, block in runs:
groups[(x1, x2, y1, block)].append(z1)
for (x1, x2, y, block), zs in sorted(groups.items()):
zs.sort(); start = end = zs[0]
for z in zs[1:]:
if z == end + 1: end = z
else:
merged.append((x1, y, start, x2, y, end, block)); start = end = z
merged.append((x1, y, start, x2, y, end, block))
result = []; groups = defaultdict(list)
for x1, y1, z1, x2, y2, z2, block in merged:
groups[(x1, x2, z1, z2, block)].append(y1)
for (x1, x2, z1, z2, block), ys in sorted(groups.items()):
ys.sort(); start = end = ys[0]
for y in ys[1:]:
if y == end + 1: end = y
else:
result.append((x1, start, z1, x2, end, z2, block)); start = end = y
result.append((x1, start, z1, x2, end, z2, block))
return result
def manifest(v):
tiles = defaultdict(dict)
for p, block in v.blocks.items():
tiles[(p[1] // 8, p[2] // 16, p[0] // 16)][p] = block
batches = []
for key, data in sorted(tiles.items()):
recipe = []
for x1, y1, z1, x2, y2, z2, block in boxes(data):
lo = dict(zip(('x', 'y', 'z'), (x1 + ORIGIN[0], y1 + ORIGIN[1], z1 + ORIGIN[2])))
hi = dict(zip(('x', 'y', 'z'), (x2 + ORIGIN[0], y2 + ORIGIN[1], z2 + ORIGIN[2])))
recipe.append({'type': 'box', 'min': lo, 'max': hi, 'block': block})
positions = list(data)
minimum = [min(p[i] for p in positions) + ORIGIN[i] for i in range(3)]
maximum = [max(p[i] for p in positions) + ORIGIN[i] for i in range(3)]
batches.append({'key': '-'.join(map(str, key)), 'blocks': len(data),
'min': dict(zip(('x', 'y', 'z'), minimum)), 'max': dict(zip(('x', 'y', 'z'), maximum)),
'recipe': {'version': 1, 'operations': recipe},
'stages': dict(Counter(v.owners[p] for p in data))})
return {'name': 'Gothic hall reference reconstruction', 'origin': ORIGIN,
'blocks': len(v.blocks), 'stages': dict(Counter(v.owners.values())),
'palette': dict(Counter(v.blocks.values())), 'batches': batches}