#!/usr/bin/env python3 """Optional authored support and tracked-vehicle pass; no runtime IDs changed. Buk-M2E/Tor-M2E are stylized tracked versions. Radar/EW/TZM are generic support vehicles; depot and light are game props, not specific prototypes. """ import json from pathlib import Path import zlib import gen_models as g ROOT = Path(g.ROOT) SOURCE = ROOT / 'source-assets/assets/airdef/models/block' def tracked(tex, tor=False): p = [g.el((-9, 4, -13), (25, 13, 30), tex), g.el((-9, 4, -16), (25, 9, -13), tex), g.el((-8, 9, -14), (24, 13, -9), tex, rot=('x', -22.5)), g.el((-8, 13, -7), (24, 15, 20), tex), g.el((-7, 13, 21), (23, 14, 30), 'dark_metal'), *g.both(g.el((-13, 0, -15), (-9, 8.5, 31), 'track')), *g.both(g.el((-13.3, 8.5, -14), (-9, 9.5, 31), tex))] for z in (-11, -5, 1, 7, 13, 19, 25): # Three broad strips for a readable round wheel over each continuous track. for a, b, w in [(-1, -.5, .7), (-.5, .5, 1), (.5, 1, .7)]: p += g.both(g.el((-13.08, 4.2 + a * 3.1, z - w * 2.5), (-13.02, 4.2 + b * 3.1, z + w * 2.5), 'hub')) # Different deck fittings distinguish the two tracked vehicles. if tor: p += [g.el((-7, 15, -10), (2, 15.7, -5), 'dark_metal'), g.el((15, 14, 21), (22, 15, 28), 'metal')] else: p += [g.el((-7, 15, -10), (2, 15.7, -5), 'metal'), g.el((3, 14, 23), (20, 14.5, 28), 'bare_nozzle')] return p def buk_turret(): # Phased-array face and two shoulders, while the rail pack keeps its pivot. return [g.el((-4, 0, -4), (20, 7, 20), 'pilot_olive'), g.el((-2, 7, 2), (18, 10, 18), 'pilot_olive'), g.el((1, 2, -10), (11, 14, -4), 'radar_face'), g.el((0, 1, -5), (16, 3, -3), 'dark_metal'), *g.both(g.el((-4, 7, 4), (-2, 9, 16), 'metal'))] def buk_rails(): # Keep four rails and their exact centres and forward endpoints. return [{**p, 'tex': 'pilot_olive' if p['tex'] == 'ru_green' else p['tex']} for p in g.buk_rails()] def tor_turret(): # Eight existing cells; the engine's launch grid is intentionally unchanged. p = [g.el((-4, 0, -6), (20, 12, 22), 'pilot_olive'), g.el((-3, 12, -3), (19, 16, 21), 'pilot_olive'), g.el((-2, 2, -11), (18, 15, -6), 'radar_face'), g.el((-3, 16, 2), (19, 17, 20), 'dark_metal')] for i in range(4): for j in range(2): x, z = -1 + i * 5, 4 + j * 8 p.append(g.el((x, 16.5, z), (x + 4, 17.5, z + 6), 'tube_cap')) return p def truck(kind): # Generic four-axle service truck, based on the project's existing dimensions. p = [g.el((-5, 5, -15), (21, 9, 32), 'pilot_olive'), g.el((-5, 9, -16), (21, 16, -6), 'pilot_olive'), g.el((-4.5, 16, -15.5), (20.5, 20, -6), 'pilot_olive'), g.el((-4, 16, -15.99), (7.5, 19.3, -15.8), 'glass_cab'), g.el((8.5, 16, -15.99), (20, 19.3, -15.8), 'glass_cab'), *g.both(g.el((-5.08, 16, -14), (-4.99, 19, -8), 'glass_cab')), g.el((-5.5, 7, -16), (21.5, 8, -15.3), 'dark_metal'), *g.both(g.el((-4, 9, -16), (-1, 10.3, -15.8), 'bare_lamp'))] for z in (-11, -2, 17, 26): for a, b, w in [(-1, -.6, .6), (-.6, -.2, .92), (-.2, .2, 1), (.2, .6, .92), (.6, 1, .6)]: p += g.both(g.el((-8.5, 4 + a * 4, z - w * 4), (-5.5, 4 + b * 4, z + w * 4), 'tire')) p += g.both(g.el((-8.56, 2.2, z - 1.8), (-8.5, 5.8, z + 1.8), 'hub')) if kind == 'radar': p += [g.el((-4, 9, -5), (20, 12, 30), 'pilot_olive'), g.el((-3, 12, 4), (19, 14, 28), 'dark_metal'), *g.both(g.el((-7, 0, 23), (-5, 6, 25), 'metal'))] elif kind == 'tzm': p += [g.el((-4, 9, -3), (20, 12, 30), 'dark_metal'), g.el((18, 12, -4), (21, 26, -1), 'metal'), g.el((6, 24, -4), (21, 26, -1), 'metal'), g.el((6, 20, -4), (7, 24, -3), 'dark_metal')] for x in (-3, 5, 13): p += [g.el((x, 12, -1), (x + 6, 18, 29), 'tube', {'north':'tube_cap', 'south':'tube_cap'})] else: p += [g.el((-5, 9, -4), (21, 22, 30), 'pilot_olive'), g.el((-4, 12, 30), (20, 20, 30.3), 'dark_metal'), g.el((7, 22, 10), (9, 32, 12), 'bare_antenna')] for y in (24, 28): p += [g.el((-4, y, 9), (20, y + 1, 13), 'bare_antenna')] return p def depot(): p = [g.el((-14, 0, -14), (30, 2, 30), 'concrete'), g.el((-13, 2, -13), (1, 12, 1), 'crate'), g.el((-13, 2, 3), (1, 10, 17), 'crate'), g.el((3, 2, -13), (17, 10, 1), 'crate'), g.el((-14, 2, 27), (30, 5, 29), 'dark_metal'), g.el((-14, 2, -14), (-12, 5, 28), 'dark_metal')] for k in range(3): p.append(g.el((6, 2 + k * 5, 6), (28, 7 + k * 5, 10), 'tube', {'north':'tube_cap','south':'tube_cap'})) return p def searchlight(): return [g.el((1, 0, 1), (15, 2, 15), 'dark_metal'), g.el((3, 2, 3), (13, 4, 13), 'metal'), g.el((6, 4, 6), (10, 12, 10), 'dark_metal'), *g.both(g.el((2, 8, 7), (3, 14, 9), 'metal'))] def searchlight_head(): return [g.el((3, 3, -2), (13, 13, 14), 'dark_metal'), g.el((4, 2, 0), (12, 14, 12), 'dark_metal'), g.el((3.5, 3.5, -2.5), (12.5, 12.5, -2), 'lamp'), g.el((5, 5, 14), (11, 11, 15), 'metal')] def export(name, parts, burnt=False): model = g.model(parts, burnt=burnt) for key, value in model['textures'].items(): rel = value.split(':', 1)[1] + '.png' if not any((root / rel).exists() for root in (ROOT / 'assets/airdef/textures', ROOT / 'source-assets/assets/airdef/textures')): base = g.VARIANTS[key][0] candidate = base + '_b' if burnt else base if not (ROOT / 'assets/airdef/textures/block' / (candidate + '.png')).exists(): candidate = base model['textures'][key] = 'airdef:block/' + candidate SOURCE.mkdir(parents=True, exist_ok=True) (SOURCE / (name + '.json')).write_text(json.dumps(model, indent=1) + '\n', encoding='utf-8') if __name__ == '__main__': for name, make in [('buk_chassis', lambda: tracked('pilot_olive')), ('buk_turret', buk_turret), ('buk_rails', buk_rails), ('tor_chassis', lambda: tracked('pilot_olive', True)), ('tor_turret', tor_turret), ('radar_truck', lambda: truck('radar')), ('tzm_truck', lambda: truck('tzm')), ('ew_truck', lambda: truck('ew')), ('depot', depot), ('searchlight', searchlight), ('searchlight_head', searchlight_head)]: parts = make() export(name, parts) export(name + '_burnt', g.burnt_parts(parts, zlib.crc32(name.encode())), True) print(name, len(parts))