Separate map projects and add reusable island and operator camera tools
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Read-only trapdoor panel/contact checks for actual or overlaid block readers.
|
||||
|
||||
Pass a ``state_reader(x, y, z)`` returning canonical block data. Each fixture is
|
||||
``{'pos': [x,y,z], 'support': [x,y,z]}`` for an adjacent support cell, or uses
|
||||
``'edge': 'south'`` for an intended edge within the trapdoor's own cell.
|
||||
Support materials and partial support shapes must be validated by the caller;
|
||||
this check measures contact with that cell's face and rejects an empty cell.
|
||||
|
||||
Shape source: pinned Paper 26.2 TrapDoorBlock.getShape / static initializer,
|
||||
Block.boxZ(16,13,16), Shapes.rotateAll. An open NORTH trapdoor occupies the
|
||||
SOUTH 3/16 strip. HALF affects closed panels only. No world/file/network writes.
|
||||
|
||||
>>> world = {(0, 0, 0): 'minecraft:spruce_trapdoor[facing=east,half=bottom,open=true]', (1, 0, 0): 'minecraft:spruce_planks'}
|
||||
>>> read = lambda x, y, z: world.get((x,y,z), 'minecraft:air')
|
||||
>>> check_contact(read, (0,0,0), support=(1,0,0))['gap_blocks']
|
||||
0.8125
|
||||
>>> world[(0,0,0)] = 'minecraft:spruce_trapdoor[open=true,facing=west,half=top]'
|
||||
>>> check_contact(read, (0,0,0), support=(1,0,0))['passed']
|
||||
True
|
||||
>>> check_contact(read, (0,0,0), edge='north')['passed']
|
||||
False
|
||||
>>> world[(0,0,0)] = 'minecraft:iron_trapdoor[open=false,facing=south,half=top]'
|
||||
>>> check_contact(read, (0,0,0), edge='up')['passed']
|
||||
True
|
||||
"""
|
||||
|
||||
NORMALS = {'west': (-1, 0, 0), 'east': (1, 0, 0),
|
||||
'down': (0, -1, 0), 'up': (0, 1, 0),
|
||||
'north': (0, 0, -1), 'south': (0, 0, 1)}
|
||||
THICKNESS = 3 / 16
|
||||
AIR = {'minecraft:air', 'minecraft:cave_air', 'minecraft:void_air'}
|
||||
|
||||
|
||||
def panel_bounds(state):
|
||||
"""Return local AABB minimum/maximum and its thickness axis (X=0,Y=1,Z=2)."""
|
||||
name, separator, raw = state.partition('[')
|
||||
if not name.startswith('minecraft:') or not name.endswith('_trapdoor') or not separator or not raw.endswith(']'):
|
||||
raise ValueError('Expected explicit trapdoor block data')
|
||||
values = dict(part.split('=', 1) for part in raw[:-1].split(','))
|
||||
if values.get('open') == 'true':
|
||||
# Bounds are in block units; the panel sits opposite its facing.
|
||||
strips = {'north': (2, 1 - THICKNESS, 1), 'south': (2, 0, THICKNESS),
|
||||
'east': (0, 0, THICKNESS), 'west': (0, 1 - THICKNESS, 1)}
|
||||
if values.get('facing') not in strips:
|
||||
raise ValueError('Open trapdoor needs a horizontal facing')
|
||||
axis, start, end = strips[values['facing']]
|
||||
elif values.get('open') == 'false' and values.get('half') in {'top', 'bottom'}:
|
||||
axis = 1
|
||||
start, end = (1 - THICKNESS, 1) if values['half'] == 'top' else (0, THICKNESS)
|
||||
else:
|
||||
raise ValueError('Trapdoor needs explicit open and valid half properties')
|
||||
minimum, maximum = [0., 0., 0.], [1., 1., 1.]
|
||||
minimum[axis], maximum[axis] = start, end
|
||||
return minimum, maximum, axis
|
||||
|
||||
|
||||
def _position(pos):
|
||||
result = tuple(pos)
|
||||
if len(result) != 3 or any(type(value) is not int for value in result):
|
||||
raise ValueError('Position must contain three integer block coordinates')
|
||||
return result
|
||||
|
||||
|
||||
def check_contact(state_reader, pos, *, support=None, edge=None):
|
||||
"""Measure a panel's gap/alignment to a requested edge or adjacent cell face.
|
||||
|
||||
``passed`` requires the broad panel face to be parallel and flush. Merely
|
||||
touching with a thin side edge does not pass. A support fixture also requires
|
||||
a nonair observed support; this is not a full support collision-shape audit.
|
||||
"""
|
||||
pos = _position(pos)
|
||||
if (support is None) == (edge is None):
|
||||
raise ValueError('Specify exactly one adjacent support or intended edge')
|
||||
support_state = None
|
||||
if support is not None:
|
||||
support = _position(support)
|
||||
delta = tuple(target - origin for origin, target in zip(pos, support))
|
||||
edge = next((name for name, normal in NORMALS.items() if normal == delta), None)
|
||||
if edge is None:
|
||||
raise ValueError('Support must share one face with the trapdoor cell')
|
||||
support_state = state_reader(*support)
|
||||
if edge not in NORMALS:
|
||||
raise ValueError('Unknown intended edge')
|
||||
state = state_reader(*pos)
|
||||
minimum, maximum, thickness_axis = panel_bounds(state)
|
||||
normal = NORMALS[edge]
|
||||
axis = next(i for i, value in enumerate(normal) if value)
|
||||
gap = minimum[axis] if normal[axis] < 0 else 1 - maximum[axis]
|
||||
parallel = axis == thickness_axis
|
||||
support_present = support_state is None or support_state.partition('[')[0] not in AIR
|
||||
result = {'pos': list(pos), 'state': state, 'edge': edge,
|
||||
'panel_bounds': {'min': minimum, 'max': maximum},
|
||||
'panel_parallel_to_face': parallel, 'gap_blocks': gap,
|
||||
'passed': parallel and gap == 0 and support_present}
|
||||
if support is not None:
|
||||
result.update(support=list(support), support_state=support_state,
|
||||
support_present=support_present)
|
||||
return result
|
||||
|
||||
|
||||
def audit_contacts(state_reader, fixtures):
|
||||
"""Return a compact summary and per-fixture geometric evidence."""
|
||||
checks = []
|
||||
for fixture in fixtures:
|
||||
result = check_contact(state_reader, fixture['pos'], support=fixture.get('support'), edge=fixture.get('edge'))
|
||||
if 'name' in fixture:
|
||||
result['name'] = fixture['name']
|
||||
checks.append(result)
|
||||
failures = [check for check in checks if not check['passed']]
|
||||
return {'passed': not failures, 'checked': len(checks), 'failed': len(failures),
|
||||
'max_gap_blocks': max((check['gap_blocks'] for check in checks), default=0),
|
||||
'checks': checks}
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import doctest
|
||||
result = doctest.testmod()
|
||||
raise SystemExit(bool(result.failed))
|
||||
Reference in New Issue
Block a user