Files
probably-decentralized/art/sprites.py
T
ZHU MINCHAOandClaude Opus 5.5 750b26e4b3 Mod: mining rig, graphics cards, trading terminal, nightly settlement
First playable slice, tested in game on the test save:
- Network upgrade "Mining Access" (300) delivers a trading terminal, a
  mining rig and an RZX 50.
- The rig is a machine with a battery slot and four card slots; each night
  it draws its total power from the battery and its cards mine PSCoin.
  Cards show hashrate, power draw and condition, wear every night and can
  burn out. All state lives on item tags or in the mod save data.
- Trading terminal with wallet, trade (four pairs, deposit credits as CRC,
  withdraw CRC through the compliance desk) and report pages.
- Nightly log lines; exchange opening and halving news on the paper.
- English and Chinese text; sprites generated by art/sprites.py.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 09:11:46 +08:00

169 lines
6.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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)