Implement 32x32 BFS visitor navigation and daily contracts system with companion .npc journals; add native assembly scenes for Clover slots, Pyramid cards, Blackjack Party and Russian Party; package animation and sprite packs; add comprehensive offline test suites and rebuild shipping binaries.
56 lines
2.5 KiB
Python
56 lines
2.5 KiB
Python
"""Offline art normalization to the exact assembly table and ball anchors."""
|
|
from pathlib import Path
|
|
import struct
|
|
from PIL import Image
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
SOURCE = ROOT / "assets/sprites/pyramid"
|
|
OUT = ROOT / "assets/animations/pyramid"
|
|
|
|
|
|
def pack(name, frames):
|
|
w, h = frames[0].size
|
|
payload = b"".join(frame.tobytes("raw", "BGRA") for frame in frames)
|
|
(OUT / name).write_bytes(struct.pack("<4s4I", b"ANM1", w, h, len(frames), len(payload)) + payload)
|
|
|
|
|
|
def main():
|
|
OUT.mkdir(parents=True, exist_ok=True)
|
|
source = Image.open(SOURCE / "table-v2.png").convert("RGBA")
|
|
# Nine-slice-style normalization keeps the mouths and cushion noses aligned
|
|
# with the shared logical cloth rectangle, rather than scaling the whole art.
|
|
source_x = [0, 128, 160, 723, 1290, 1320, 1448]
|
|
source_y = [0, 197, 220, 733, 747, 1086]
|
|
target_x = [0, 60, 61, 320, 579, 580, 640]
|
|
target_y = [0, 100, 101, 359, 360, 480]
|
|
frame = Image.new("RGBA", (640, 480))
|
|
for row in range(len(target_y) - 1):
|
|
for col in range(len(target_x) - 1):
|
|
cell = source.crop((round(source_x[col] * source.width / 1448),
|
|
round(source_y[row] * source.height / 1086),
|
|
round(source_x[col + 1] * source.width / 1448),
|
|
round(source_y[row + 1] * source.height / 1086)))
|
|
cell = cell.resize((target_x[col + 1] - target_x[col],
|
|
target_y[row + 1] - target_y[row]), Image.Resampling.NEAREST)
|
|
frame.paste(cell, (target_x[col], target_y[row]))
|
|
frame.putalpha(255)
|
|
pack("table-v2.bin", [frame])
|
|
sheet = Image.open(SOURCE / "balls-v1.png").convert("RGBA")
|
|
frames = []
|
|
for i in range(8):
|
|
col, row = i % 4, i // 4
|
|
cell = sheet.crop((round(col * sheet.width / 4), round(row * sheet.height / 2),
|
|
round((col + 1) * sheet.width / 4), round((row + 1) * sheet.height / 2)))
|
|
# Ignore faint generator fringe outside the actual circular ball.
|
|
cell.putalpha(cell.getchannel("A").point(lambda a: 255 if a >= 192 else 0))
|
|
cell = cell.crop(cell.getchannel("A").getbbox())
|
|
cell.thumbnail((10, 10), Image.Resampling.LANCZOS)
|
|
frame = Image.new("RGBA", (12, 12))
|
|
frame.paste(cell, ((12 - cell.width) // 2, (12 - cell.height) // 2))
|
|
frames.append(frame)
|
|
pack("balls-v1.bin", frames)
|
|
print("Packed anchored 640x480 table and eight 12x12 Russian ball poses")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|