#!/usr/bin/env python3 """Generate NOMEANSNO QUEST sprites as PNGs — stdlib only (zlib + struct). Sprites are ASCII grids: each char maps to an RGB in the sprite's legend, '.' is transparent. MRPCI quantizes them per-room, so colors here are intent, not law. Run from the repo root: python3 tools/gen_sprites.py """ import os import struct import zlib OUT = os.path.join(os.path.dirname(__file__), "..", "sprites") def write_png(path, rows, legend, scale=1): rows = [r for r in rows for _ in range(scale)] h = len(rows) w = max(len(r) for r in rows) * scale raw = b"" for y in range(h): raw += b"\x00" # filter: none row = rows[y].ljust(w // scale, ".") for ch in row: px = b"\x00\x00\x00\x00" if ch == "." else struct.pack("BBBB", *legend[ch], 255) raw += px * scale def chunk(tag, data): c = struct.pack(">I", len(data)) + tag + data return c + struct.pack(">I", zlib.crc32(tag + data) & 0xFFFFFFFF) png = b"\x89PNG\r\n\x1a\n" png += chunk(b"IHDR", struct.pack(">IIBBBBB", w, h, 8, 6, 0, 0, 0)) png += chunk(b"IDAT", zlib.compress(raw, 9)) png += chunk(b"IEND", b"") with open(path, "wb") as f: f.write(png) print(f" {os.path.basename(path)} {w}x{h}") S = {} # name -> (rows, legend) # --- people ------------------------------------------------------------- # Rob Wright — the ego, as an MRPCI sprite sheet (`-sheet2x3`): 2 walk # frames x 3 direction loops (front, back, side; the engine mirrors side # for right-facing). Every cel is exactly 12x20; feet = bottom center. ROB_LEGEND = { "h": (72, 52, 36), # hair "f": (214, 178, 148),# face "2": (40, 40, 48), # eyes "s": (52, 56, 64), # dark shirt "p": (44, 48, 88), # jeans "o": (30, 30, 34), # boots / strap "b": (150, 60, 40), # bass body "r": (220, 200, 160),# bass neck } ROB_FRONT0 = [ "....hhhh....", "....hhhh....", "....ffff....", "....f2f2....", "....ffff....", ".....ff.....", "...ssssss...", "..ssssssss..", "..s.ssss.s..", "..s.ssss.s..", "..b.ssss....", ".bbbrssss...", ".bb.ssss....", "..b.pppp....", "....pppp....", "....p..p....", "....p..p....", "....p..p....", "....p..p....", "...oo..oo...", ] # Frame 2: the walk bob — boots swing in. ROB_FRONT1 = ROB_FRONT0[:19] + ["....oo.oo..."] ROB_BACK0 = [ "....hhhh....", "....hhhh....", "....hhhh....", "....hhhh....", ".....ff.....", "...ssssss...", "..sossssss..", "..s.osss.s..", "..s.soss.s..", "....ssos....", "....ssso....", "....sssso...", "....ssss....", "....pppp....", "....pppp....", "....p..p....", "....p..p....", "....p..p....", "....p..p....", "...oo..oo...", ] ROB_BACK1 = ROB_BACK0[:19] + ["....oo.oo..."] # Side faces LEFT (MRPCI mirrors it for right); bass rides the front hip. ROB_SIDE0 = [ "....hhhh....", "....hhhh....", "....ffff....", "....2fff....", "....ffff....", ".....ff.....", "....ssss....", "...ssssss...", "...ssssss...", "...ssssss...", "..brssss....", ".rbbssss....", "..b.ssss....", "....pppp....", "....pppp....", "....pp......", "....pp......", "....pp......", "....pp......", "...oooo.....", ] ROB_SIDE1 = ROB_SIDE0[:15] + [ "...p..p.....", "...p..p.....", "..p....p....", "..p....p....", ".oo....oo...", ] def sheet(*loops): """Stack loops vertically, frames horizontally — the -sheetCxR layout.""" rows = [] for cels in loops: for cel in cels: assert len(cel) == len(cels[0]), "frames in a loop must share height" assert all(len(r) == len(cel[0]) for r in cel), "cels must be rectangular" for y in range(len(cels[0])): rows.append("".join(c[y] for c in cels)) return rows S["rob-sheet2x3"] = ( sheet([ROB_FRONT0, ROB_FRONT1], [ROB_BACK0, ROB_BACK1], [ROB_SIDE0, ROB_SIDE1]), ROB_LEGEND, ) # John Wright — behind the kit or beside it; sticks in hand. S["john"] = ( [ "...hhhhh...", "...hhhhh...", "...ffff....", "...f2f2....", "...ffff....", "....ff.....", "..tttttt...", ".tttttttt..", ".t.tttt.t..", ".w.tttt.w..", "...tttt....", "...pppp....", "...p..p....", "...p..p....", "...p..p....", "..oo..oo...", ], { "h": (60, 44, 30), "f": (216, 182, 152), "2": (40, 40, 48), "t": (140, 40, 44), # red tee "w": (222, 214, 180),# drumsticks "p": (40, 42, 50), "o": (30, 30, 34), }, ) S["mom"] = ( [ "...hhhhh...", "..hhhhhhh..", "...ffff....", "...f2f2....", "...ffff....", "....ff.....", "..dddddd...", ".dddddddd..", ".d.aaaa.d..", "...aaaa....", "...aaaa....", "...dddd....", "...d..d....", "..oo..oo...", ], { "h": (168, 160, 150), "f": (216, 184, 156), "2": (50, 44, 40), "d": (90, 110, 140), # housedress "a": (228, 226, 214),# apron "o": (60, 50, 44), }, ) # Joey Shithead of D.O.A., on stage. Mohawk not optional. S["joey"] = ( [ "....kk.....", "...kkkk....", "....kk.....", "...ffff....", "...f2f2....", "...ffff....", "....ff.....", "..lllllll..", ".lllllllll.", ".l.lllll.l.", ".g.lllll...", ".gg.lll....", "...pppp....", "...p..p....", "...p..p....", "..oo..oo...", ], { "k": (30, 160, 60), # green mohawk "f": (210, 172, 140), "2": (30, 30, 36), "l": (36, 36, 40), # leather "g": (170, 120, 60), # guitar neck "p": (46, 46, 52), "o": (26, 26, 30), }, ) S["biafra"] = ( [ "...hhhhh...", "...hhhhh...", "...ffff....", "...f2f2....", "...ffff....", "....ff.....", "..jjjjjj...", ".jjjjjjjj..", ".j.tttt.j..", "...tttt....", "...tttt....", "...pppp....", "...p..p....", "...p..p....", "..oo..oo...", ], { "h": (50, 38, 30), "f": (214, 178, 148), "2": (36, 40, 48), "j": (70, 60, 90), # thrift blazer "t": (200, 60, 50), # loud tee "p": (50, 50, 58), "o": (30, 30, 34), }, ) S["stagehand"] = ( [ "...ccccc...", "...ffff....", "...f2f2....", "...ffff....", "....ff.....", "..vvvvvv...", ".vvvvvvvv..", ".v.vvvv.v..", "...vvvv....", "...vvvv....", "...pppp....", "...p..p....", "...p..p....", "..oo..oo...", ], { "c": (40, 40, 48), # headset cap "f": (208, 172, 140), "2": (36, 36, 44), "v": (240, 180, 40), # hi-vis "p": (44, 46, 54), "o": (28, 28, 32), }, ) S["mobster"] = ( [ "...bbbbb...", "...ffff....", "...f2f2....", "...ffff....", "...ffnf....", "....ff.....", ".ttttttttt.", ".tttttttte.", ".t.tttt.te.", ".t.tttt.t..", "...tttt....", "...tttt....", "...ssss....", "...s..s....", "...s..s....", "..oo..oo...", ], { "b": (30, 30, 34), # flat cap "f": (206, 168, 138), "2": (30, 34, 40), "n": (150, 110, 90), # that nose "t": (60, 90, 60), # tracksuit "e": (220, 200, 80), # gold stripe "s": (54, 56, 60), "o": (26, 26, 30), }, ) # --- portraits (24x24, drawn 2x in core windows, 6x in the GUI) ---------- S["john-face"] = ( [ "bbbbbbbbbbbbbbbbbbbbbbbb", "bbbbbbbbbbbbbbbbbbbbbbbb", "bbbbbhhhhhhhhhhhhhbbbbbb", "bbbbhhhhhhhhhhhhhhhbbbbb", "bbbhhhhhhhhhhhhhhhhhbbbb", "bbbhhfffffffffffffhhbbbb", "bbbhffffffffffffffhhbbbb", "bbbhffffffffffffffhbbbbb", "bbbhff22ffffff22ffhbbbbb", "bbbhff22ffffff22ffhbbbbb", "bbbbffffffffffffffbbbbbb", "bbbbffffffnnffffffbbbbbb", "bbbbffffffnnffffffbbbbbb", "bbbbffffffffffffffbbbbbb", "bbbbfffmmmmmmmmfffbbbbbb", "bbbbffffmmmmmmffffbbbbbb", "bbbbbffffffffffffbbbbbbb", "bbbbbbffffffffffbbbbbbbb", "bbbbbbbttttttttbbbbbbbbb", "bbbbbbttttttttttbbbbbbbb", "bbbbbttttttttttttbbbbbbb", "bbbbttttttttttttttbbbbbb", "bbbbttttttttttttttbbbbbb", "bbbbbbbbbbbbbbbbbbbbbbbb", ], { "b": (24, 26, 34), "h": (60, 44, 30), "f": (216, 182, 152), "2": (40, 40, 48), "n": (190, 150, 122), "m": (150, 90, 80), "t": (140, 40, 44), }, ) S["mom-face"] = ( [ "bbbbbbbbbbbbbbbbbbbbbbbb", "bbbbbhhhhhhhhhhhhhbbbbbb", "bbbhhhhhhhhhhhhhhhhbbbbb", "bbhhhhhhhhhhhhhhhhhhbbbb", "bbhhhffffffffffffhhhbbbb", "bbhhffffffffffffffhhbbbb", "bbhhffffffffffffffhhbbbb", "bbhhff22ffffff22ffhhbbbb", "bbhhff22ffffff22ffhhbbbb", "bbhhffffffffffffffhhbbbb", "bbhhffffffnnffffffhhbbbb", "bbhhffffffffffffffhhbbbb", "bbhhfffmmmmmmmmfffhhbbbb", "bbhhffffmmmmmmffffhhbbbb", "bbbhhffffffffffffhhbbbbb", "bbbhhhffffffffffhhhbbbbb", "bbbbhhhhffffffhhhhbbbbbb", "bbbbbbddddddddddbbbbbbbb", "bbbbbddddddddddddbbbbbbb", "bbbbddddddddddddddbbbbbb", "bbbbddddaaaaaaddddbbbbbb", "bbbbdddaaaaaaaadddbbbbbb", "bbbbdddaaaaaaaadddbbbbbb", "bbbbbbbbbbbbbbbbbbbbbbbb", ], { "b": (26, 26, 32), "h": (168, 160, 150), "f": (216, 184, 156), "2": (50, 44, 40), "n": (192, 154, 128), "m": (160, 100, 90), "d": (90, 110, 140), "a": (228, 226, 214), }, ) S["biafra-face"] = ( [ "bbbbbbbbbbbbbbbbbbbbbbbb", "bbbbhhhhhhhhhhhhhhbbbbbb", "bbbhhhhhhhhhhhhhhhhbbbbb", "bbbhhhhhhhhhhhhhhhhbbbbb", "bbbhhffffffffffffhhbbbbb", "bbbhffffffffffffffhbbbbb", "bbbhffffffffffffffhbbbbb", "bbbhffeeffffffeeffhbbbbb", "bbbhff22ffffff22ffhbbbbb", "bbbhffffffffffffffhbbbbb", "bbbhffffffnnffffffhbbbbb", "bbbhffffffffffffffhbbbbb", "bbbbffffffffffffffbbbbbb", "bbbbfffmmmmmmmmfffbbbbbb", "bbbbffmmmmmmmmmmffbbbbbb", "bbbbbfffmmmmmmfffbbbbbbb", "bbbbbbffffffffffbbbbbbbb", "bbbbbbbjjjjjjjjbbbbbbbbb", "bbbbbjjjjjjjjjjjjbbbbbbb", "bbbbjjjjttttttjjjjbbbbbb", "bbbbjjjttttttttjjjbbbbbb", "bbbbjjjttttttttjjjbbbbbb", "bbbbjjjttttttttjjjbbbbbb", "bbbbbbbbbbbbbbbbbbbbbbbb", ], { "b": (28, 24, 34), "h": (50, 38, 30), "f": (214, 178, 148), "2": (36, 40, 48), "e": (90, 90, 110), # that stare, pre-twinkle "n": (188, 148, 120), "m": (150, 84, 76), # the grin "j": (70, 60, 90), "t": (200, 60, 50), }, ) S["mobster-face"] = ( [ "bbbbbbbbbbbbbbbbbbbbbbbb", "bbbbkkkkkkkkkkkkkkbbbbbb", "bbbkkkkkkkkkkkkkkkkbbbbb", "bbbkkkkkkkkkkkkkkkkbbbbb", "bbbbffffffffffffffbbbbbb", "bbbfffffffffffffffbbbbbb", "bbbff2222ffff2222ffbbbbb", "bbbff2222ffff2222ffbbbbb", "bbbffffffffffffffffbbbbb", "bbbfffffnnnnnnfffffbbbbb", "bbbfffffnnnnnnfffffbbbbb", "bbbffffffffffffffffbbbbb", "bbbbffffffffffffffbbbbbb", "bbbbfffmmmmmmmmfffbbbbbb", "bbbbffffffffffffffbbbbbb", "bbbbbffffffffffffbbbbbbb", "bbbbbbffffffffffbbbbbbbb", "bbbbbbbttttttttbbbbbbbbb", "bbbbbttttttttttttbbbbbbb", "bbbbtttteeeeeetttbbbbbbb", "bbbbttttttttttttttbbbbbb", "bbbbttttttttttttttbbbbbb", "bbbbttttttttttttttbbbbbb", "bbbbbbbbbbbbbbbbbbbbbbbb", ], { "b": (24, 28, 26), "k": (30, 30, 34), "f": (206, 168, 138), "2": (30, 34, 40), "n": (150, 110, 90), "m": (120, 70, 64), "t": (60, 90, 60), "e": (220, 200, 80), }, ) # --- props ---------------------------------------------------------------- S["portastudio"] = ( [ "gggggggggggggggggggggggggg", "gssssssssssssssssssssssssg", "gsrrrrrssgggggggssrrrrrssg", "gsr...rssgddddggssr...rssg", "gsr.o.rssgddddggssr.o.rssg", "gsr...rssgggggggssr...rssg", "gsrrrrrssssssssssssrrrrssg", "gsssssssskkkkkkkksssssssg.", "gsswwswwsswwsswwsswwswwsg.", "gsswwswwsswwsswwsswwswwsg.", "gggggggggggggggggggggggg..", ], { "g": (70, 72, 78), "s": (98, 100, 108), "r": (50, 50, 56), "o": (200, 190, 170), "d": (180, 60, 50), # VU meters "k": (36, 36, 40), "w": (210, 208, 200),# faders }, ) S["castlebox"] = ( [ "cccccccccccccccccccccc", "cttttttttttttttttttttc", "ct__CASTLE__(RIP)___tc", "cttttttttttttttttttttc", "cccccccccccccccccccccc", "cccccccccccccccccccccc", "cccccccccccccccccccccc", "cccccccccccccccccccccc", ], { "c": (150, 110, 66), "t": (120, 86, 50), "_": (120, 86, 50), "C": (60, 44, 30), "A": (60, 44, 30), "S": (60, 44, 30), "T": (60, 44, 30), "L": (60, 44, 30), "E": (60, 44, 30), "R": (60, 44, 30), "I": (60, 44, 30), "P": (60, 44, 30), "(": (60, 44, 30), ")": (60, 44, 30), }, ) S["flyer"] = ( [ "wwwwwwwwww", "wkkkkkkkkw", "wwwwwwwwww", "wkkkwwkkww", "wwwwwwwwww", "wkkkkkwwww", "wwwwwwwwww", ], {"w": (226, 222, 210), "k": (40, 40, 44)}, ) S["drumkit"] = ( [ "........cc..........cc........", ".........c..........c.........", "....hhhhhhhhh..hhhhhhhhh......", "....h.......h..h.......h......", "....hhhhhhhhh..hhhhhhhhh......", "..bbbbbbbbbbbbbbbbbbbbbbbb....", ".bbwwwwwwwwwwwwwwwwwwwwwwbb...", ".bbwwwwwwwwwwwwwwwwwwwwwwbb...", ".bbwwwwwwwwwwwwwwwwwwwwwwbb...", ".bbbbbbbbbbbbbbbbbbbbbbbbbb...", "...ss...ss........ss...ss.....", "..ss.....ss......ss.....ss....", ], { "c": (210, 190, 120), # cymbals "h": (120, 60, 50), # toms "b": (140, 40, 44), # kick shell "w": (230, 226, 214), # kick head "s": (140, 140, 150), }, ) S["rig"] = ( [ "kkkkkkkkkkkkkkkkkkkkkkkkkkkk", "kggggggggggggkkggggggggggggk", "kg..........gkkg..........gk", "kg.oooooooo.gkkg.oooooooo.gk", "kg.oooooooo.gkkg.oooooooo.gk", "kg..........gkkg..........gk", "kkkkkkkkkkkkkkkkkkkkkkkkkkkk", "kggggggggggggkkggggggggggggk", "kg.oooooooo.gkkg.oooooooo.gk", "kg.oooooooo.gkkg.oooooooo.gk", "kg..........gkkg..........gk", "kkkkkkkkkkkkkkkkkkkkkkkkkkkk", "kkrrkkkkkkkkkkkkkkkkkkkkrrkk", ], { "k": (30, 30, 34), "g": (56, 56, 62), "o": (80, 76, 70), # speaker cones "r": (200, 60, 50), # power lamps }, ) S["van"] = ( [ "..........wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww..........", "........wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww........", ".......wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww.......", ".......wggggggwwwwwwwwwwwwwwwwwwwwwwwwwwwggggggww.......", ".......wggggggwwwwwwwwwwwwwwwwwwwwwwwwwwwggggggww.......", ".......wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww.......", ".......wwwwkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkwwwww.......", ".......wwwwkNOMEANSNOwwwwwwwwwwwwwwwwwwwwwwkwwwww.......", ".......wwwwkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkwwwww.......", ".......wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww.......", "........bb..tttt..bbbbbbbbbbbbbbbbbb..tttt..bb...........", "........bb.tttttt.bbbbbbbbbbbbbbbbbb.tttttt.bb...........", "............tttt......................tttt..............", ], { "w": (216, 214, 206), "g": (110, 140, 160), # windows "k": (60, 60, 66), # hand-painted band stripe "N": (220, 60, 50), "O": (220, 60, 50), "M": (220, 60, 50), "E": (220, 60, 50), "A": (220, 60, 50), "S": (220, 60, 50), "b": (70, 70, 76), "t": (30, 30, 34), }, ) S["dumpster"] = ( [ "gggggggggggggggggggggggggggggggggggg", "ggllllllllllllllllllllllllllllllllgg", "gg..............................gg..", "ggllllllllllllllllllllllllllllllgg..", "gg..............................gg..", "gggggggggggggggggggggggggggggggggg..", "..kk..........................kk....", "..kk..........................kk....", ], {"g": (60, 90, 70), "l": (80, 112, 88), "k": (30, 30, 34)}, ) def main(): os.makedirs(OUT, exist_ok=True) print("writing sprites:") for name, (rows, legend) in S.items(): # People and props render 2x (320-wide rooms); portraits and the # tiny flyer stay 1:1. scale = 1 if name.endswith("-face") or name == "flyer" else 2 write_png(os.path.join(OUT, f"{name}.png"), rows, legend, scale) if __name__ == "__main__": main()