#!/usr/bin/env python3 """物品像素图(游戏物品为 100 PPU,一格 16 像素;形状由不透明像素决定)。 用法:python3 art/sprites.py → 写出 src/Decentralized/ModFiles/Textures/Items/*.png python3 art/sprites.py --preview DIR → 另存放大 6 倍、桌面底色的预览 画法沿用 Probably Automated 对讲机图:轮廓统一用 #292234,光从左上来,颜色取自游戏物品图集的调色板。 """ import os import struct import sys import zlib C = { 'O': (0x29, 0x22, 0x34), # 轮廓 'k': (0x37, 0x35, 0x41), # 最暗 'd': (0x44, 0x46, 0x4C), # 暗面 'm': (0x5F, 0x68, 0x68), # 中间 'b': (0x88, 0x94, 0x8A), # 亮面 'l': (0xA8, 0xAE, 0xA5), # 受光 'h': (0xC4, 0xC4, 0xB0), # 亮边 'g': (0x53, 0x58, 0x5B), # 散热片 's': (0x27, 0x56, 0x54), # 屏幕底 'S': (0x67, 0xAA, 0xA5), # 屏幕字 'T': (0x8A, 0xBF, 0xBB), # 屏幕高光 'G': (0x5E, 0x9C, 0x4E), # Probidia 绿 'g2': (0x3E, 0x6B, 0x36), 'R': (0xB0, 0x4A, 0x3E), # PMD 红 'r2': (0x7A, 0x33, 0x2E), 'Y': (0xC8, 0xA4, 0x4A), # 金手指 'c': (0x2E, 0x2A, 0x2A), # 焦黑 'e': (0x6B, 0x4A, 0x3A), # 焦痕 } class Canvas: def __init__(self, w, h): self.w, self.h = w, h self.px = [[None] * w for _ in range(h)] def put(self, x, y, c): if 0 <= x < self.w and 0 <= y < self.h: self.px[y][x] = c def rect(self, x0, y0, x1, y1, c): for y in range(y0, y1 + 1): for x in range(x0, x1 + 1): self.put(x, y, c) def box(self, x0, y0, x1, y1, fill, outline='O'): self.rect(x0, y0, x1, y1, fill) for x in range(x0, x1 + 1): self.put(x, y0, outline); self.put(x, y1, outline) for y in range(y0, y1 + 1): self.put(x0, y, outline); self.put(x1, y, outline) def png(self, path, scale=1, bg=None): raw = bytearray() for row in self.px: line = bytearray() for c in row: r, g, b, a = (0, 0, 0, 0) if c is None else C[c] + (255,) if bg is not None: r, g, b = (round(v * a / 255 + w * (255 - a) / 255) for v, w in zip((r, g, b), bg)) a = 255 line += bytes((r, g, b, a)) * scale for _ in range(scale): raw.append(0); raw += line def chunk(tag, data): return struct.pack('>I', len(data)) + tag + data + struct.pack('>I', zlib.crc32(tag + data) & 0xFFFFFFFF) data = b'\x89PNG\r\n\x1a\n' + chunk(b'IHDR', struct.pack('>IIBBBBB', self.w * scale, self.h * scale, 8, 6, 0, 0, 0)) \ + chunk(b'IDAT', zlib.compress(bytes(raw), 9)) + chunk(b'IEND', b'') os.makedirs(os.path.dirname(os.path.abspath(path)), exist_ok=True) with open(path, 'wb') as f: f.write(data) def gpu(accent, accent_dark, burnt=False): """显卡(32×16,2×1 格):外壳、两个风扇、品牌色条、金手指。""" c = Canvas(32, 16) c.box(1, 2, 30, 12, 'm') c.rect(2, 3, 29, 3, 'l') # 顶边受光 c.rect(2, 11, 29, 11, 'd') # 底边暗面 for cx in (9, 21): # 风扇 for y in range(5, 11): for x in range(cx - 4, cx + 5): dx, dy = x - cx, y - 7.5 if dx * dx + dy * dy <= 16: c.put(x, y, 'k') if 6 <= dx * dx + dy * dy <= 16: c.put(x, y, 'g') c.put(cx, 7, 'b'); c.put(cx, 8, 'b') c.rect(2, 4, 29, 4, accent) # 品牌色条 c.put(2, 5, accent_dark); c.put(29, 5, accent_dark) c.rect(6, 13, 25, 13, 'Y') # 金手指 for x in range(6, 26, 2): c.put(x, 14, 'Y') c.rect(30, 3, 30, 11, 'k') # 挡板 if burnt: for x, y in [(7, 5), (8, 6), (10, 9), (19, 6), (20, 7), (22, 9), (14, 8), (15, 7), (24, 5)]: c.put(x, y, 'c') c.rect(2, 4, 29, 4, 'e') for x, y in [(12, 3), (13, 3), (17, 11), (26, 10)]: c.put(x, y, 'c') return c def rig(): """矿架(48×48,3×3 格):开放式机架,四层显卡位,左下电源。""" c = Canvas(48, 48) c.box(2, 3, 45, 44, None) for y in range(4, 44): c.put(3, y, 'b'); c.put(44, y, 'd') for x in range(3, 45): c.put(x, 4, 'l'); c.put(x, 43, 'k') for i, y in enumerate((8, 16, 24, 32)): # 四层卡位 c.rect(6, y, 41, y + 5, 'k') c.rect(6, y, 41, y, 'g') for x in range(8, 40, 3): c.put(x, y + 2, 'g'); c.put(x, y + 3, 'g') c.put(40, y + 2, 'G' if i % 2 == 0 else 'R') c.box(6, 38, 17, 42, 'd') # 电源 c.put(15, 40, 'S') for x in range(20, 42, 2): # 底部走线 c.put(x, 40, 'm') c.rect(2, 45, 45, 46, 'k') # 脚 return c def terminal(): """交易终端(32×32,2×2 格):小型显示器 + 键盘,屏幕上是 K 线。""" c = Canvas(32, 32) c.box(3, 2, 28, 21, 'm') c.rect(4, 3, 27, 3, 'l') c.box(5, 4, 26, 18, 's') candles = [(8, 12, 15), (11, 10, 14), (14, 11, 13), (17, 7, 12), (20, 8, 11), (23, 5, 10)] for x, top, bot in candles: c.rect(x, top, x, bot, 'S') c.put(x, top - 1, 'T') c.rect(13, 22, 18, 23, 'd') # 支架 c.box(2, 24, 29, 30, 'b') # 键盘 for y in (26, 28): for x in range(4, 28, 2): c.put(x, y, 'm') return c SPRITES = { 'gpu_probidia': lambda: gpu('G', 'g2'), 'gpu_pmd': lambda: gpu('R', 'r2'), 'gpu_burnt': lambda: gpu('e', 'c', burnt=True), 'rig': rig, 'terminal': terminal, } if __name__ == '__main__': here = os.path.dirname(os.path.abspath(__file__)) if len(sys.argv) == 3 and sys.argv[1] == '--preview': for name, make in SPRITES.items(): make().png(os.path.join(sys.argv[2], name + '.png'), scale=6, bg=(0xA0, 0xA8, 0x9C)) print(sys.argv[2]) else: out = os.path.normpath(os.path.join(here, '..', 'src', 'Decentralized', 'ModFiles', 'Textures', 'Items')) for name, make in SPRITES.items(): make().png(os.path.join(out, name + '.png')) print(out)