Files
airdef/tools/pilot_models.py
T
chexit 966b52f9fc
build / mod (push) Failing after 18s
feat: add shared radar batteries and animated cold launch
Separate search and engagement roles, support launcher-only SAMs, animate individual tube lids and delay ground strike spending until ejection. Preserve battery configuration and launch poses in saves and replay.

Co-authored-by: Codex <codex@openai.com>
2026-11-02 23:40:40 +03:00

138 lines
6.6 KiB
Python

#!/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():
# Compact 8x8 TEL: full-width cab, narrow rails and a clear gap between axles.
# The launcher grid and vehicle origin remain untouched.
parts = [g.el((-2, 5, -15), (18, 9, 32), "pilot_olive"),
g.el((-3, 9, -16), (19, 15, -7), "pilot_olive"),
g.el((-2.5, 15, -15), (18.5, 19, -7), "pilot_olive"),
g.el((-2, 15.2, -15.95), (7.5, 18, -15.8), "glass_cab"),
g.el((8.5, 15.2, -15.95), (18, 18, -15.8), "glass_cab"),
g.el((-3.06, 14, -14), (-2.95, 17.5, -8), "glass_cab"),
g.el((18.95, 14, -14), (19.06, 17.5, -8), "glass_cab"),
g.el((-2.5, 19, -15), (18.5, 19.4, -7), "pilot_olive"),
g.el((-2, 9, -6.5), (18, 11, 7), "pilot_olive"),
g.el((0, 9.1, 7), (16, 10, 28), "dark_metal"),
g.el((-3, 9, 28), (19, 12, 32), "pilot_olive"),
g.el((-3.5, 7, -16), (19.5, 8, -15.4), "dark_metal")]
for z in [-10.5, -1.5, 16.5, 25.5]:
parts += tire(-6, -3, z, 4.1)
parts += g.both(g.el((-5, 7, 4), (-2.5, 10, 12), "dark_metal"))
parts += g.both(g.el((-4.5, 10, -14), (-3, 10.8, -12), "bare_lamp"))
parts += g.both(g.el((-4, 12, -8.2), (-3, 12.6, -7.8), "dark_metal"))
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", {"south": "tube_cap"}, omit_faces=("north",)))
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((-9, 4, -8), (25, 13.5, 30), "pilot_nato"),
g.el((-9, 4, -15), (25, 9, -8), "pilot_nato"),
g.el((-8.5, 9, -14), (24.5, 13, -7), "pilot_nato", rot=("x", -22.5)),
g.el((-8, 13.5, -4), (24, 15.2, 18), "pilot_nato"),
g.el((-7, 13.5, 18), (23, 14.5, 30), "dark_metal"),
g.el((-6, 15.3, -6), (3, 15.9, -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), "pilot_nato"))
for z in [20, 24, 28]:
parts += [g.el((-6, 14.6, z), (22, 14.8, 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), "pilot_nato"),
g.el((-2.5, 5, -5), (18.5, 12, 15), "pilot_nato"),
g.el((-2, 12, -2), (18, 13, 14), "pilot_nato"),
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), "pilot_nato", 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 gepard_guns():
# Preserve both bore/aim origins; match the new turret's painted housings.
return [{**part, "tex": "pilot_nato" if part["tex"] == "flecktarn" else part["tex"]}
for part in g.gepard_guns()]
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, 28, 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 any((root / (value.split(":", 1)[1] + ".png")).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 [("s400_chassis", s400_chassis), ("s400_tubes", s400_tubes),
("gepard_hull", gepard_hull), ("gepard_turret", gepard_turret), ("gepard_guns", gepard_guns), ("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")