e84a5e9fa7
Preserve launcher grids and authored materials; fix the Buk radar intersection and regenerate geometry bounds. Register the separate RM48U game visual for the upcoming carrier path. Co-Authored-By: Codex <noreply@openai.com>
147 lines
6.3 KiB
Python
147 lines
6.3 KiB
Python
#!/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))
|