#!/usr/bin/env python3 """Optional authored vehicle pass. Does not run during normal regeneration. Stylistic references: KAMAZ-based Pantsir-S1, HEMTT M983/M901 Patriot, truck-mounted IRIS-T SLM, and NASAMS multi-missile launcher. Existing part origins and launch grids are intentionally retained; these are not scale replicas. """ 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 wheels(zs, x=-8.5, radius=4): out = [] for z in zs: for a, b, w in [(-1, -.6, .6), (-.6, -.2, .92), (-.2, .2, 1), (.2, .6, .92), (.6, 1, .6)]: out += g.both(g.el((x, radius + a * radius, z - radius * w), (x + 3, radius + b * radius, z + radius * w), 'tire')) out += g.both(g.el((x - .06, radius - 1.8, z - 1.8), (x, radius + 1.8, z + 1.8), 'hub')) return out def cab(tex, top=20): # Split windscreen and side windows on a full-width forward-control cab. return [g.el((-5, 8, -16), (21, top - 4, -6), tex), g.el((-4.5, top - 4, -15.5), (20.5, top, -6), tex), g.el((-4, top - 4, -15.99), (7.5, top - .7, -15.82), 'glass_cab'), g.el((8.5, top - 4, -15.99), (20, top - .7, -15.82), 'glass_cab'), *g.both(g.el((-5.08, top - 4, -14.5), (-4.99, top - 1, -8), 'glass_cab')), g.el((-5.5, 7, -16), (21.5, 8, -15.3), 'dark_metal'), *g.both(g.el((-4.5, 9, -16), (-1.5, 10.2, -15.8), 'bare_lamp'))] def pantsir_truck(): # KAMAZ 8x8 family: four axles, short front cab, equipment volume under turret. p = [g.el((-5, 5, -15), (21, 9, 32), 'pilot_olive'), *cab('pilot_olive', 20), g.el((-4, 9, -5), (20, 16.5, 5), 'pilot_olive'), g.el((-3, 9, 5), (19, 12, 28), 'dark_metal'), g.el((-5, 8, 28), (21, 11, 32), 'pilot_olive'), *g.both(g.el((-7, 8, 6), (-5, 11, 14), 'dark_metal'))] return p + wheels([-11, -1.5, 17, 26]) def pantsir_turret(): # Maintain the radar/mast and gun-pack attachment volumes. return [g.el((-2, 0, -5), (18, 7, 16), 'pilot_olive'), g.el((0, 7, -4), (16, 10, 14), 'pilot_olive'), g.el((1, 1, -11), (15, 11, -6), 'radar_face'), g.el((2, 1, -6), (14, 11, -5), 'dark_metal'), g.el((7, 10, 4), (9, 17, 6), 'metal'), g.el((1, 10, 8), (15, 12, 16), 'pilot_olive'), g.el((5, 10, -4), (11, 13, 0), 'dark_metal'), g.el((6, 10.5, -4.4), (10, 12.5, -4), 'bare_lamp')] def pantsir_pack(): # Keep every barrel and canister position; match the turret's material. return [{**part, 'tex': 'pilot_olive' if part['tex'] == 'ru_green' else part['tex']} for part in g.pantsir_pack()] def patriot_truck(): # HEMTT M983 family; existing generator had only three axles. p = [g.el((-5, 5, -15), (21, 9, 32), 'pilot_nato'), *cab('pilot_nato', 22), g.el((-4, 9, -5), (20, 15, 7), 'pilot_nato'), g.el((-3, 9, 8), (19, 11, 28), 'dark_metal'), g.el((2, 11, 18), (14, 12, 26), 'metal'), *g.both(g.el((-7, 9, 1), (-5, 12, 9), 'dark_metal'))] return p + wheels([-11, -2, 17, 26]) def patriot_trailer(): p = [g.el((-6, 6, -16), (22, 9, 32), 'pilot_nato'), g.el((-4, 9, -12), (20, 11, 24), 'dark_metal'), g.el((-1, 11, 14), (17, 16, 30), 'metal'), g.el((-7, 3, -16), (23, 7, -12), 'dark_metal'), *g.both(g.el((-8, 0, -6), (-6, 6, -4), 'metal'))] return p + wheels([19, 27], -9, 3.7) def irist_chassis(): # Four-axle truck with ISO-frame equipment deck, preserving launcher origin. p = [g.el((-5, 5, -15), (21, 9, 32), 'pilot_nato'), *cab('pilot_nato', 20), g.el((-4, 9, -5), (20, 11, 29), 'pilot_nato'), g.el((-3, 11, -4), (19, 13, 28), 'dark_metal'), *g.both(g.el((-7, 6, 4), (-5, 9, 14), 'dark_metal')), *g.both(g.el((-7, 0, 25), (-5, 6, 27), 'metal'))] return p + wheels([-11, -2, 17, 26]) def nasams_trailer(): # Towable, low frame, two axles and separate rear mounting base. p = [g.el((-5, 5, -10), (21, 8, 26), 'pilot_nato'), g.el((-4, 8, -8), (20, 9, 23), 'dark_metal'), g.el((-4, 9, 10), (20, 13, 24), 'pilot_nato'), g.el((6, 3, -16), (10, 6, -10), 'metal'), *g.both(g.el((-7, 0, -6), (-5, 5, -4), 'metal')), *g.both(g.el((-7, 0, 23), (-5, 5, 25), 'metal'))] return p + wheels([8, 18], -8.5, 3.7) def package(nx): # Exactly the existing Grid spacing and tube end positions. p = g.tubes(nx, 2, 4, 'canister') for z in (-2, 22): p.append(g.el((8 - (nx * 5 - 1) / 2 - .4, 0, z), (8 + (nx * 5 - 1) / 2 + .4, 9, z + .5), 'dark_metal')) return p 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, wreck in [('pantsir_truck', pantsir_truck, True), ('pantsir_turret', pantsir_turret, True), ('pantsir_pack', pantsir_pack, True), ('patriot_truck', patriot_truck, False), ('patriot_trailer', patriot_trailer, True), ('irist_chassis', irist_chassis, True), ('irist_tubes', lambda: package(4), True), ('nasams_trailer', nasams_trailer, True), ('nasams_launcher', lambda: package(3), True)]: parts = make() export(name, parts) if wreck: export(name + '_burnt', g.burnt_parts(parts, zlib.crc32(name.encode())), True) print(name, len(parts))