squad-proto/tools/generate_static_assets.py

146 lines
7.4 KiB
Python
Raw Normal View History

"""Deterministic sci-fi/body-horror environment, prop and inventory icon sources."""
from __future__ import annotations
from pathlib import Path
import random
from PIL import Image, ImageDraw
ROOT = Path(__file__).resolve().parents[1]
WORLD = ROOT / "assets/sources/world"
ICONS = ROOT / "assets/sources/items"
MANIFEST = ROOT / "assets/manifests/static.generated.yaml"
INK = (13, 17, 22, 255)
STEEL = (61, 72, 79, 255)
EDGE = (27, 34, 39, 255)
LIGHT = (103, 119, 121, 255)
FLESH = (91, 32, 42, 255)
VEIN = (159, 48, 55, 255)
SICK = (121, 145, 78, 255)
def save(image: Image.Image, root: Path, name: str) -> Path:
root.mkdir(parents=True, exist_ok=True)
path = root / f"{name}.png"
image.save(path)
return path
def floor(name: str, mode: str, seed: int) -> None:
rng = random.Random(seed)
im = Image.new("RGBA", (32, 16)); d = ImageDraw.Draw(im)
poly = [(15, 0), (31, 8), (15, 15), (0, 8)]
d.polygon(poly, fill=(49, 58, 63, 255), outline=EDGE)
for _ in range(30):
x, y = rng.randrange(32), rng.randrange(16)
if im.getpixel((x, y))[3]:
q = rng.randrange(-10, 9); c = im.getpixel((x, y))
im.putpixel((x, y), tuple(max(0, min(255, c[i]+q)) for i in range(3))+(255,))
if mode == "clean":
d.line([(7, 4), (23, 12)], fill=(75, 87, 91, 255)); d.point((15, 3), fill=LIGHT)
d.point((5, 8), fill=LIGHT); d.point((25, 8), fill=LIGHT)
elif mode == "cracked":
d.line([(18, 2), (15, 6), (18, 8), (12, 13)], fill=(12, 15, 18, 255), width=1)
d.line([(15, 6), (10, 7)], fill=(12, 15, 18, 255))
elif mode == "grate":
d.polygon([(10, 5), (16, 2), (23, 6), (16, 11)], fill=(20, 27, 30, 255), outline=(91, 104, 104, 255))
for x in range(11, 22, 3): d.line([(x, 4), (x-1, 9)], fill=(74, 91, 89, 255))
else:
d.ellipse((6, 4, 25, 13), fill=(62, 27, 35, 255), outline=FLESH)
for _ in range(8):
x = rng.randrange(8, 24); y = rng.randrange(5, 12)
d.line([(16, 8), (x, y)], fill=VEIN)
d.ellipse((13, 6, 18, 10), fill=(137, 50, 57, 255))
save(im, WORLD, name)
def wall(name: str, infested: bool) -> None:
im = Image.new("RGBA", (32, 32)); d = ImageDraw.Draw(im)
d.polygon([(15, 0), (31, 8), (15, 16), (0, 8)], fill=(72, 82, 85, 255), outline=EDGE)
d.polygon([(0, 8), (15, 16), (15, 31), (0, 23)], fill=(35, 43, 48, 255), outline=INK)
d.polygon([(15, 16), (31, 8), (31, 23), (15, 31)], fill=(48, 57, 61, 255), outline=INK)
d.line([(4, 10), (4, 21), (12, 25)], fill=(80, 92, 92, 255))
d.line([(27, 11), (27, 20), (19, 25)], fill=(27, 34, 38, 255))
for p in ((5, 11), (12, 15), (26, 12), (19, 16)): d.rectangle((p[0],p[1],p[0]+1,p[1]+1), fill=LIGHT)
if infested:
d.ellipse((7, 7, 24, 27), fill=(70, 27, 35, 235), outline=FLESH)
d.ellipse((11, 11, 20, 22), fill=(111, 36, 44, 255), outline=VEIN)
d.ellipse((14, 14, 17, 18), fill=(32, 14, 19, 255))
for end in ((2,9),(29,9),(4,25),(27,25)):
d.line([(15,17), end], fill=VEIN, width=1)
save(im, WORLD, name)
def prop(name: str, kind: str) -> None:
sizes = {"crate": (24,22), "barrel": (18,25), "locker": (18,32)}
w,h=sizes[kind]; im=Image.new("RGBA",(w,h)); d=ImageDraw.Draw(im)
if kind == "crate":
d.polygon([(1,6),(11,1),(22,6),(12,11)],fill=(82,78,64,255),outline=INK)
d.polygon([(1,6),(12,11),(12,21),(1,16)],fill=(48,48,43,255),outline=INK)
d.polygon([(12,11),(22,6),(22,16),(12,21)],fill=(61,59,50,255),outline=INK)
d.line([(3,7),(10,10),(10,18),(3,15),(3,7)],fill=(112,105,78,255))
elif kind == "barrel":
d.ellipse((2,1,15,7),fill=(77,82,77,255),outline=INK)
d.rectangle((2,4,15,20),fill=(50,59,57,255),outline=INK)
d.ellipse((2,17,15,24),fill=(42,48,47,255),outline=INK)
d.line([(2,9),(15,9)],fill=LIGHT); d.line([(2,17),(15,17)],fill=INK)
d.polygon([(6,10),(10,8),(12,12),(9,16),(5,14)],fill=SICK)
else:
d.polygon([(2,5),(9,1),(16,5),(9,9)],fill=(75,82,84,255),outline=INK)
d.polygon([(2,5),(9,9),(9,31),(2,27)],fill=(38,46,50,255),outline=INK)
d.polygon([(9,9),(16,5),(16,27),(9,31)],fill=(52,61,64,255),outline=INK)
d.line([(11,11),(11,27)],fill=INK); d.point((13,18),fill=(180,61,56,255))
save(im,WORLD,"prop_"+name)
def icon(name: str) -> None:
im=Image.new("RGBA",(24,24)); d=ImageDraw.Draw(im)
d.rounded_rectangle((1,1,22,22),3,fill=(20,25,30,230),outline=(72,84,88,255))
metal=(151,165,166,255); dark=(45,52,55,255); red=(164,48,54,255)
if name in {"rifle","smg","shotgun","pistol","magnum"}:
length={"pistol":11,"magnum":12,"smg":15,"shotgun":18,"rifle":18}[name]
d.rectangle((3,9,3+length,12),fill=metal); d.rectangle((7,12,10,17),fill=dark)
if name=="shotgun": d.line((4,14,19,14),fill=(113,77,50,255),width=2)
elif name == "bow":
d.arc((4,3,18,21),270,90,fill=metal,width=2); d.line((11,4,11,20),fill=(104,124,103,255))
elif name in {"saber","knife","cleaver"}:
end=(18,4) if name!="knife" else (15,7); d.line((6,18,*end),fill=metal,width=3)
d.line((4,19,9,16),fill=(118,76,43,255),width=2)
elif name in {"jacket","vest","plate","shroud"}:
col={"jacket":(75,83,77,255),"vest":(82,91,91,255),"plate":(115,122,119,255),"shroud":(56,49,63,255)}[name]
d.polygon([(7,5),(4,9),(6,20),(18,20),(20,9),(17,5),(14,8),(10,8)],fill=col,outline=INK)
elif name in {"bandage","stimpack"}:
d.rectangle((7,4,16,20),fill=(178,188,175,255),outline=INK)
d.rectangle((9,10,14,14),fill=red); d.line((11,8,11,16),fill=(220,220,205,255))
else:
colors={"lantern":(224,184,72,255),"charm":(142,69,101,255),"spurs":(143,151,151,255)}
d.ellipse((6,5,18,18),fill=colors[name],outline=INK); d.ellipse((10,9,14,13),fill=(235,221,159,255))
save(im,ICONS,"item_icon_"+name)
def manifest() -> None:
entries=[]
for name in ("floor_clean","floor_cracked","floor_grate","floor_biomass","wall_industrial","wall_infested",
"prop_crate","prop_barrel","prop_locker"):
pivot = '"15,8"' if name.startswith("floor") else ("\"15,31\"" if name.startswith("wall") else "bottom_center")
entries += [f"{name}:", " backend: import", f" source: ../sources/world/{name}.png", f" pivot: {pivot}",
f" tags: [environment, {'floor' if name.startswith('floor') else 'prop' if name.startswith('prop') else 'wall'}]", ""]
names=("rifle","pistol","shotgun","smg","magnum","bow","saber","knife","cleaver","jacket","vest","plate",
"shroud","lantern","charm","spurs","bandage","stimpack")
for name in names:
aid="item_icon_"+name; entries += [f"{aid}:"," backend: import",f" source: ../sources/items/{aid}.png"," pivot: center"," tags: [ui, item]",""]
MANIFEST.write_text("\n".join(["version: 1","defaults:"," dirs: 1"," fps: 1",""]+entries),encoding="utf-8",newline="\n")
def main() -> None:
for i,mode in enumerate(("clean","cracked","grate","biomass")): floor("floor_"+mode,mode,100+i)
wall("wall_industrial",False); wall("wall_infested",True)
for kind in ("crate","barrel","locker"): prop(kind,kind)
for name in ("rifle","pistol","shotgun","smg","magnum","bow","saber","knife","cleaver","jacket","vest","plate",
"shroud","lantern","charm","spurs","bandage","stimpack"): icon(name)
manifest(); print("generated 27 static assets and assets/manifests/static.generated.yaml")
if __name__ == "__main__": main()