KAZIK/tools/generators/pack_scratch_assets.py
Игорь Федосеев ca62db53e9 feat: implement native billiards, scratch tickets, first-person dice, and city wake scene
- Billiards: add native Russian Pyramid & American Eight-Ball pool with physics prediction, controls, and regression tests
- Scratch Tickets: add dedicated counter, mat presentation, elliptical coin scraping, 4 ticket types, and payout effects
- First-Person Dice: add native dice scene, hand anchor frames, throwing animation, and table perspective
- City Wake & Weather: add arrival cutscene, alley geometry, weather/rain effects, and lighter/rocker animation
- World Dressing: add hall paintings, furniture packs, hall lighting, and scene regression tests
- Build & Tooling: update build.bat for modern MSVC toolchain, update launcher hash, and refresh shipping binaries
2026-10-16 12:37:40 +03:00

68 lines
2.7 KiB
Python

"""Offline normalization only; scratch masks, input and prizes run in MASM."""
from pathlib import Path
import json
import struct
from PIL import Image
import pack_city_wake
ROOT = Path(__file__).resolve().parents[2]
SRC = ROOT / "assets/sprites/scratch"
OUT = ROOT / "assets/animations/scratch"
def opaque(image):
image = image.convert("RGBA")
image.putalpha(image.getchannel("A").point(lambda a: 255 if a >= 160 else 0))
return image
def pack(name, frames):
w,h = frames[0].size
raw = b"".join(f.tobytes("raw","BGRA") for f in frames)
(OUT / name).write_bytes(struct.pack("<4s4I",b"ANM1",w,h,len(frames),len(raw))+raw)
def main():
OUT.mkdir(parents=True,exist_ok=True)
table = Image.open(SRC / "table-v1.png").convert("RGBA").resize((640,480),Image.Resampling.NEAREST)
table.putalpha(255)
pack("table-v1.bin",[table])
tickets = []
for name in ("ticket-v1.png", "ticket-numbers-v1.png", "ticket-rows-v1.png", "ticket-instant-v1.png"):
ticket = opaque(Image.open(SRC / name))
tickets.append(ticket.crop(ticket.getbbox()).resize((448,288),Image.Resampling.NEAREST))
pack("ticket-v1.bin",tickets)
foil = Image.open(SRC / "foil-v1.png").convert("RGBA").resize((104,42),Image.Resampling.BOX)
foil.putalpha(255)
pack("foil-v1.bin",[foil])
pack_city_wake.SRC = SRC
bodies = pack_city_wake.figures("coin-edge-v1.png",4)
scale = min(48/max(b.width for b in bodies),56/max(b.height for b in bodies))
frames = []
for body in bodies:
body = body.resize((round(body.width*scale),round(body.height*scale)),Image.Resampling.NEAREST)
bottom = body.height-1
xs = [x for x in range(body.width) if body.getpixel((x,bottom))[3]]
anchor = (min(xs)+max(xs))//2
frame = Image.new("RGBA",(64,64))
frame.paste(body,(32-anchor,60-bottom))
frames.append(frame)
pack("coin-edge-v1.bin",frames)
counter = opaque(Image.open(SRC / "counter-v1.png"))
counter = counter.crop(counter.getbbox())
scale = min(224/counter.width,240/counter.height)
counter = counter.resize((round(counter.width*scale),round(counter.height*scale)),Image.Resampling.NEAREST)
frame = Image.new("RGBA",(224,240))
frame.paste(counter,((224-counter.width)//2,240-counter.height))
pack("counter-v1.bin",[frame])
for index,ticket in enumerate(tickets):
ticket.save(ROOT / f"build/scratch/ticket-{index}-normalized.png")
sheet = Image.new("RGBA",(256,64))
for i,frame in enumerate(frames):
sheet.paste(frame,(64*i,0))
sheet.save(ROOT / "build/scratch/coin-normalized.png")
print("Packed lottery paper, four edge-contact coins (hotspot 32,60), grounded ticket counter")
if __name__ == "__main__":
main()