KAZIK/tools/generators/pack_coin_assets.py
Artvasil09 4f8ae0893b Implement native slots, coin toss, European roulette and hall furniture
Add native assembly scenes and assets for Slots (777), Coin flip, and European Roulette; add 3D hall furniture packs and prompts; improve Russian UI strings in CP1251; update launcher hash and rebuild shipping binaries.
2026-10-10 14:43:30 +03:00

110 lines
4.8 KiB
Python

"""Normalize generated art offline; all toss animation and play are MASM."""
import hashlib
import json
from collections import deque
from pathlib import Path
from PIL import Image
import pack_arrival_assets as packs
ROOT = Path(__file__).resolve().parents[2]
SOURCE = ROOT / "assets/sprites/coin"
def isolated_hand(frame):
"""Remove disconnected previous-row cuff spill without changing the hand anchor."""
width, height = frame.size
alpha = frame.getchannel("A").tobytes()
visited = bytearray(width * height)
largest = []
for seed, value in enumerate(alpha):
if not value or visited[seed]:
continue
visited[seed] = 1
queue = deque([seed])
component = []
while queue:
at = queue.popleft()
component.append(at)
x, y = at % width, at // width
for dx, dy in ((-1, 0), (1, 0), (0, -1), (0, 1)):
nx, ny = x + dx, y + dy
if 0 <= nx < width and 0 <= ny < height:
neighbor = ny * width + nx
if alpha[neighbor] and not visited[neighbor]:
visited[neighbor] = 1
queue.append(neighbor)
if len(component) > len(largest):
largest = component
mask = bytearray(width * height)
for at in largest:
mask[at] = 255
frame.putalpha(Image.frombytes("L", frame.size, bytes(mask)))
return frame
def main():
packs.OUTPUT = ROOT / "assets/animations/coin"
preview = ROOT / "build/coin"
preview.mkdir(parents=True, exist_ok=True)
background = Image.open(SOURCE / "table-v1.png").convert("RGBA")
background = background.resize((640, 480), Image.Resampling.NEAREST)
# Reserve the renderer's 40-pixel header without cropping the dealer's head.
framed = Image.new("RGBA", (640, 480), (20, 25, 29, 255))
framed.paste(background.crop((0, 0, 640, 440)), (0, 40))
background = framed
background.putalpha(255)
packs.write_pack("table-v1.bin", [background])
background.save(preview / "table.png")
source = Image.open(SOURCE / "faces-v1.png").convert("RGBA")
assert source.getchannel("A").getextrema()[0] == 0
faces = []
for column in range(2):
frame = source.crop((column * source.width // 2, 0,
(column + 1) * source.width // 2, source.height))
frame = frame.crop(frame.getchannel("A").getbbox())
frame = frame.resize((128, 128), Image.Resampling.NEAREST)
frame = packs.solid_alpha(frame)
faces.append(frame)
frame.save(preview / ("face-" + str(column) + ".png"))
packs.write_pack("faces-v1.bin", faces)
source = Image.open(SOURCE / "hands-v2.png").convert("RGBA")
assert source.getchannel("A").getextrema()[0] == 0
hands = []
held_centers = [(79, 70), (76, 77), (74, 82), (77, 96), (70, 98)]
for row in range(4):
for column in range(4):
frame = source.crop((column * source.width // 4, row * source.height // 4,
(column + 1) * source.width // 4, (row + 1) * source.height // 4))
# Keep common cell scale rather than stretching each pose's alpha bounds.
frame = frame.resize((224, 224), Image.Resampling.NEAREST)
frame = packs.solid_alpha(frame)
# Remove the generator's saturated red alpha-matte fringe, not skin highlights.
frame.putdata([(r, g, b, 0 if r > 120 and g < 50 and b < 50 else a)
for r, g, b, a in frame.get_flattened_data()])
index = len(hands)
if index >= len(held_centers):
frame = isolated_hand(frame)
if index < len(held_centers):
# The held coin and fingers share one sprite and the release-point anchor.
cx, cy = held_centers[index]
aligned = Image.new("RGBA", (224, 224))
aligned.paste(frame, (88 - cx, 24 - cy))
frame = aligned
frame.putdata([(r, g, b, a) if a else (0, 0, 0, 0)
for r, g, b, a in frame.get_flattened_data()])
assert frame.getchannel("A").getbbox()
hands.append(frame)
frame.save(preview / ("hand-" + str(len(hands) - 1) + ".png"))
assert len({hashlib.sha256(frame.tobytes()).hexdigest() for frame in hands}) == 16
packs.write_pack("hands-v2.bin", hands)
(packs.OUTPUT / "manifest-v1.json").write_text(json.dumps({
"table": [640, 480, 1], "faces": [128, 128, 2], "hands": [224, 224, 16],
"hand_anchor": "held coin (88,24); empty hand shared grid cell",
"held_frames": 5, "alpha": "opaque table, binary cutouts"
}, indent=2) + "\n", encoding="ascii")
print("Packed coin table, two embossed sides and sixteen distinct hand poses")
if __name__ == "__main__":
main()