137 lines
5.1 KiB
Python
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)
|