KAZIK/tools/generators/pack_scratch_portraits.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

94 lines
3.8 KiB
Python

"""Offline import of supplied artwork and exact interactive silver-area maps."""
from pathlib import Path
from collections import deque
import json
import struct
from PIL import Image, ImageDraw
ROOT = Path(__file__).resolve().parents[2]
SRC = ROOT / "assets/sprites/scratch"
OUT = ROOT / "assets/animations/scratch"
SIZE = (540,720)
KINDS = (("neon-ru-v3.png",5,15),("ruby-ru-v3.png",6,18),("sea-ru-v3.png",4,12))
def silver_components(image):
w,h = image.size
pixels = image.load()
candidates = set()
for y in range(310,610):
for x in range(125,525):
r,g,b = pixels[x,y][:3]
if max(r,g,b)-min(r,g,b) < 22 and 105 < min(r,g,b) and max(r,g,b) < 225:
candidates.add((x,y))
components = []
while candidates:
start = candidates.pop()
queue = deque([start])
body = [start]
while queue:
x,y = queue.popleft()
for point in ((x-1,y),(x+1,y),(x,y-1),(x,y+1)):
if point in candidates:
candidates.remove(point)
queue.append(point)
body.append(point)
if len(body)>1000:
components.append(body)
return sorted(components,key=lambda body:(min(y for x,y in body)//25,min(x for x,y in body)))
def pack(name,frames):
w,h = frames[0].size
raw = b"".join(image.tobytes("raw","BGRA") for image in frames)
(OUT/name).write_bytes(struct.pack("<4s4I",b"ANM1",w,h,len(frames),len(raw))+raw)
def main():
arts,maps,layouts = [],[],[]
for filename,winning,players in KINDS:
art = Image.open(SRC/filename).convert("RGBA").resize(SIZE,Image.Resampling.LANCZOS)
art.putalpha(255)
components = silver_components(art)
assert len(components)==winning+players,(filename,len(components))
field_map = Image.new("RGBA",SIZE)
fields = []
preview = art.copy()
draw = ImageDraw.Draw(preview)
for i,body in enumerate(components):
left,top = min(x for x,y in body),min(y for x,y in body)
right,bottom = max(x for x,y in body)+1,max(y for x,y in body)+1
assert right-left<=128 and bottom-top<=96
# Close tiny texture holes without expanding into the decorated frame.
rows = {}
for x,y in body:
rows.setdefault(y,[]).append(x)
area = 0
for y,xs in rows.items():
for x in range(min(xs),max(xs)+1):
field_map.putpixel((x,y),(0,0,i+1,255))
area += 1
fields.append(dict(x=left,y=top,w=right-left,h=bottom-top,area=area))
draw.rectangle((left,top,right-1,bottom-1),outline="red")
draw.text((left+4,top+4),str(i),fill="black")
preview.save(ROOT/f"build/scratch/{filename[:-4]}-fields.png")
arts.append(art)
maps.append(field_map)
layouts.append(dict(file=filename,winning=winning,players=players,fields=fields))
pack("portraits-v3.bin",arts)
pack("portrait-fields-v3.bin",maps)
(OUT/"portrait-layouts-v3.json").write_text(json.dumps(layouts,indent=2),encoding="utf-8")
lines = ["; Generated by pack_scratch_portraits.py; source coordinates, not screen coordinates.",".data"]
lines += ["scrPortraitWinning3D dd 5,6,4","scrPortraitPlayers3D dd 15,18,12","scrPortraitFields3D label DWORD"]
for layout in layouts:
fields = layout["fields"]
for field in fields:
lines.append(" dd "+",".join(str(field[k]) for k in ("x","y","w","h")))
lines.extend([" dd 0,0,0,0"]*(24-len(fields)))
lines += [".code",""]
(ROOT/"src_graphics/scratch_portrait_fields.inc").write_text("\n".join(lines),encoding="ascii")
print("Packed 3 supplied portrait designs, 20/24/16 exact silver masks and source-space geometry")
if __name__=="__main__":
main()