#!/usr/bin/env python3 """Export the three art pilots to authored JSON. Run gen_models.py afterwards. The JSON under source-assets is the editable deliverable. This optional authoring helper only rewrites its explicitly named pilots; the normal generator never calls it. Existing origins, launcher grid and animation parts are retained. """ 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 tire(x0, x1, z, radius=4.5): """Five voxel strips: rounded silhouette, without overlapping rotated squares' star tips.""" parts = [] for lo, hi, width in [(-1, -.6, .6), (-.6, -.2, .92), (-.2, .2, 1), (.2, .6, .92), (.6, 1, .6)]: parts += g.both(g.el((x0, radius + lo * radius, z - width * radius), (x1, radius + hi * radius, z + width * radius), "tire")) parts += g.both(g.el((x0 - .05, radius - 2.2, z - 2.2), (x0, radius + 2.2, z + 2.2), "hub")) return parts def s400_chassis(): parts = [p for p in g.maz_chassis("ru_green") if p["tex"] != "tire"] for z in [-10.5, -1.5, 16.5, 25.5]: parts += tire(-9, -5.5, z) # Cab roof and inset windscreen separate the cabin from the flat cargo deck. for x0, x1 in [(-5.7, 2.7), (13.3, 21.7)]: parts += [g.el((x0, 19, -15), (x1, 19.5, -7), "ru_green"), g.el((x0 + .5, 15, -15.8), (x1 - .5, 18.5, -15.55), "glass_cab", rot=("x", 22.5))] parts += g.both(g.B(-14.5, 8.3, -12.5, -14, 8.8, -7, "dark_metal")) parts += g.both(g.B(-14.4, 13, -10.2, -14.1, 13.4, -8.4, "bare_antenna")) # Underbody rails and an engine-deck grille read at normal gameplay distance. parts += [g.el((1, 4.2, -8), (3, 5, 29), "dark_metal"), g.el((13, 4.2, -8), (15, 5, 29), "dark_metal"), g.el((-3, 13.1, -3), (19, 13.5, 3), "bare_nozzle")] return parts def s400_tubes(): parts = [] # Same four centres and 7-unit diameter as Muzzle::Grid; pivots are unchanged. for x in [.5, 8.5]: for y in [0, 8]: for lo, hi, width in [(-1, -.6, .6), (-.6, -.2, .92), (-.2, .2, 1), (.2, .6, .92), (.6, 1, .6)]: parts.append(g.el((x + 3.5 - 3.5 * width, y + 3.5 + lo * 3.5, -16), (x + 3.5 + 3.5 * width, y + 3.5 + hi * 3.5, 32), "tube", {"north": "tube_cap", "south": "tube_cap"})) for z in [-1, 26]: parts.append(g.el((x - .15, y + 2.6, z), (x + 7.15, y + 4.4, z + 1), "dark_metal")) parts += [g.el((-.5, 0, 26), (16.5, 1, 29), "dark_metal")] return parts def gepard_hull(): parts = [g.el((-10, 4, -8), (26, 15, 30), "flecktarn"), g.el((-10, 4, -15), (26, 9, -8), "flecktarn"), g.el((-9.5, 9, -14), (25.5, 13, -7), "flecktarn", rot=("x", -22.5)), g.el((-9, 15, -4), (25, 16.5, 18), "flecktarn"), g.el((-8, 15, 18), (24, 16, 30), "dark_metal"), g.el((-6, 16.6, -6), (3, 17.2, -1), "metal")] # Side tracks remain a continuous loop; visible road wheels replace a featureless slab. parts += g.both(g.el((-14, 0, -14), (-10, 8.8, 31), "track")) for z in [-10, -4, 2, 8, 14, 20, 26]: for lo, hi, width in [(-1, -.5, .65), (-.5, .5, 1), (.5, 1, .65)]: parts += g.both(g.el((-14.08, 4.3 + lo * 3.5, z - width * 2.8), (-14.02, 4.3 + hi * 3.5, z + width * 2.8), "hub")) parts += g.both(g.el((-14.5, 8.6, -14), (-10, 10, 31), "flecktarn")) for z in [20, 24, 28]: parts += [g.el((-6, 16.1, z), (22, 16.3, z + 1), "bare_nozzle")] return parts def gepard_turret(): # Preserve the aiming origin, gun pack, tracking radar and search-dish attachment. parts = [g.el((-4, 0, -5), (20, 5, 16), "flecktarn"), g.el((-2.5, 5, -5), (18.5, 12, 15), "flecktarn"), g.el((-2, 12, -2), (18, 13, 14), "flecktarn"), g.el((3, 4, -10), (13, 12, -6), "radar_face"), g.el((7, 13, 10), (9, 18, 12), "metal")] parts += g.both(g.el((-4.7, 4.8, -4), (-2.5, 10.7, 14), "flecktarn", rot=("z", 22.5))) parts += [g.el((0, 13.05, 2), (5, 13.7, 8), "dark_metal"), g.el((10, 13.05, 2), (15, 13.7, 8), "dark_metal")] parts += g.decal("mk_de", 6, 9, -4, 2, 10.5) return parts def shahed(): parts = g.ogive(-16, -9, 2.5, "shahed_nose", 4) + g.oct_z(-9, 22, 2.5, "shahed") parts += g.delta2("shahed", "kh101_belly", -2, 21, 26, 21, 12, 7.5, 8.3) parts += g.both(g.B(-20.8, 7.6, 18, -20.1, 13.2, 26, "shahed")) parts += g.oct_z(22, 28, 1.8, "dark_metal") parts += g.both(g.B(.8, 5.5, 24, 1.5, 6.6, 28, "bare_nozzle")) parts += [g.B(-1.3, 10.5, -3, 1.3, 10.8, 7, "shahed")] return parts def export(name, parts, burnt=False): model = g.model(parts, burnt=burnt) # The authoring tool reuses existing textures; it never creates untracked palette variants. for key, value in model["textures"].items(): if not (ROOT / "assets/airdef/textures" / (value.split(":", 1)[1] + ".png")).exists(): 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 [("s400_chassis", s400_chassis), ("s400_tubes", s400_tubes), ("gepard_hull", gepard_hull), ("gepard_turret", gepard_turret), ("shahed", shahed)]: parts = make() export(name, parts) if name != "shahed": export(name + "_burnt", g.burnt_parts(parts, zlib.crc32(name.encode())), burnt=True) print(name, len(parts), "elements")