RECORDANDRONCO/tools/gen_placeholders.py
2026-07-20 04:34:17 +10:00

137 lines
5.1 KiB
Python

#!/usr/bin/env python3
"""Generate placeholder sprite PNGs for Phase A (stdlib only).
OpenRA PngSheet slices multi-frame sheets via an embedded tEXt chunk
"FrameSize: w,h" (see engine PngSheetLoader.cs). Real art replaces these in
Phase D — keep the filenames.
"""
import os, struct, zlib
OUT = os.path.join(os.path.dirname(__file__), '..', 'mods', 'vinylgod', 'sequences', 'assets')
PINK = (255, 45, 150, 255)
DARK = (30, 26, 32, 255)
GREY = (90, 85, 95, 255)
CLEAR = (0, 0, 0, 0)
def chunk(tag, data):
return struct.pack('>I', len(data)) + tag + data + struct.pack('>I', zlib.crc32(tag + data))
def write_png(path, w, h, px, text=None):
raw = b''.join(b'\x00' + b''.join(bytes(px[y * w + x]) for x in range(w)) for y in range(h))
out = b'\x89PNG\r\n\x1a\n' + chunk(b'IHDR', struct.pack('>IIBBBBB', w, h, 8, 6, 0, 0, 0))
for k, v in (text or {}).items():
out += chunk(b'tEXt', k.encode() + b'\x00' + v.encode())
out += chunk(b'IDAT', zlib.compress(raw)) + chunk(b'IEND', b'')
open(path, 'wb').write(out)
print(f'wrote {path} {w}x{h}')
def canvas(w, h, fill=CLEAR):
return [fill] * (w * h)
def rect(px, w, x0, y0, x1, y1, c):
for y in range(y0, y1):
for x in range(x0, x1):
px[y * w + x] = c
def disc(px, w, cx, cy, r, c):
for y in range(int(cy - r), int(cy + r) + 1):
for x in range(int(cx - r), int(cx + r) + 1):
if (x - cx) ** 2 + (y - cy) ** 2 <= r * r:
px[y * w + x] = c
def box_sprite(path, size, body, accent):
px = canvas(size, size)
m = size // 8
rect(px, size, m, m, size - m, size - m, body)
rect(px, size, m, m, size - m, m + 2, accent) # top trim
disc(px, size, size / 2, size / 2, size // 5, accent) # centre disc
write_png(path, size, size, px)
os.makedirs(OUT, exist_ok=True)
# wax: 12 density frames, 32x32, horizontal strip
W, H, N = 32, 32, 12
px = canvas(W * N, H)
blob = [(9, 9), (22, 12), (14, 21), (24, 24), (7, 24), (17, 7), (26, 17), (11, 15),
(20, 19), (15, 27), (5, 16), (27, 8)]
for f in range(N):
for i in range(f + 1):
cx, cy = blob[i]
for dx in range(W): # draw into frame f's slice
pass
gx = f * W + cx
disc(px, W * N, gx, cy, 3, PINK if i % 3 == 0 else DARK)
write_png(os.path.join(OUT, 'wax.png'), W * N, H, px, text={'FrameSize': '32,32'})
box_sprite(os.path.join(OUT, 'digger.png'), 24, PINK, DARK)
box_sprite(os.path.join(OUT, 'pressplant.png'), 64, DARK, PINK)
box_sprite(os.path.join(OUT, 'ampstack.png'), 32, GREY, PINK)
box_sprite(os.path.join(OUT, 'vault.png'), 32, DARK, GREY)
# Phase B: production actors
GREEN = (80, 200, 120, 255)
BLUE = (80, 140, 220, 255)
GOLD = (230, 190, 60, 255)
box_sprite(os.path.join(OUT, 'popupvan.png'), 24, GOLD, DARK)
box_sprite(os.path.join(OUT, 'popupshop.png'), 64, GOLD, PINK)
box_sprite(os.path.join(OUT, 'merchtable.png'), 32, GREEN, DARK)
box_sprite(os.path.join(OUT, 'garage.png'), 64, BLUE, DARK)
box_sprite(os.path.join(OUT, 'djgrunt.png'), 16, PINK, DARK)
# Phase C: combat roster
RED = (220, 70, 60, 255)
box_sprite(os.path.join(OUT, 'spinner.png'), 20, RED, DARK)
box_sprite(os.path.join(OUT, 'stacktank.png'), 28, DARK, RED)
box_sprite(os.path.join(OUT, 'basscannon.png'), 26, GREY, RED)
box_sprite(os.path.join(OUT, 'roadie.png'), 16, GOLD, DARK)
box_sprite(os.path.join(OUT, 'mastering.png'), 64, (150, 150, 160, 255), PINK)
def icon(path, body):
w, h = 64, 48
px = canvas(w, h)
rect(px, w, 2, 2, w - 2, h - 2, DARK)
rect(px, w, 4, 4, w - 4, h - 4, body)
disc(px, w, w / 2, h / 2, 10, DARK)
disc(px, w, w / 2, h / 2, 3, body)
write_png(path, w, h, px)
for name, col in [('popupshop', GOLD), ('ampstack', GREY), ('pressplant', (255, 45, 150, 255)),
('vault', (70, 65, 75, 255)), ('merchtable', GREEN), ('garage', BLUE),
('digger', PINK), ('popupvan', GOLD), ('djgrunt', (255, 120, 180, 255)),
('spinner', RED), ('stacktank', (120, 40, 35, 255)), ('basscannon', (160, 90, 85, 255)),
('roadie', GOLD), ('mastering', (150, 150, 160, 255))]:
icon(os.path.join(OUT, f'icon_{name}.png'), col)
# clock overlay for production progress (single frame, semi-transparent)
px = canvas(48, 48, (0, 0, 0, 110))
disc(px, 48, 24, 24, 10, (255, 45, 150, 180))
write_png(os.path.join(OUT, 'clock.png'), 48, 48, px)
# production type tab icons: rows building/infantry/vehicle x cols normal/disabled/alert
# ponytail: 64x64 because chrome textures must be power-of-two
w, h = 64, 64
px = canvas(w, h)
cols = [PINK, GREY, (255, 255, 255, 255)]
for r, shape in enumerate(('rect', 'dot', 'disc')):
for c, col in enumerate(cols):
ox, oy = c * 16, r * 16
if shape == 'rect':
rect(px, w, ox + 3, oy + 3, ox + 13, oy + 13, col)
elif shape == 'dot':
disc(px, w, ox + 8, oy + 8, 3, col)
else:
disc(px, w, ox + 8, oy + 8, 6, col)
disc(px, w, ox + 8, oy + 8, 2, DARK)
CHROME = os.path.join(os.path.dirname(__file__), '..', 'mods', 'vinylgod', 'chrome', 'assets')
write_png(os.path.join(CHROME, 'production-icons.png'), w, h, px)