KAZIK/tools/generators/pack_door_walk_v2.py
Artvasil09 51c6512675 feat: implement secret fortune teller room, lived-in den v3, and bar camera stabilization
- Secret Fortune Teller Room: arcade lantern toggle, sliding timber panel, dedicated staircase and compact cellar chamber

- Lived-in den pass v3: cloth hangings, draped workbench, props/clutter, centered entrance, and custom light sources

- Bar camera stabilization: remove alcohol horizon sway while retaining drink animations

- Synchronize binaries and update launcher hash to EADBA9C8C9BD5B7E3CC4CE486ED647C1CE9383D1DCB6EBC94FE755EA1F4BF927
2026-10-20 17:29:52 +03:00

74 lines
3.5 KiB
Python

"""Offline normalization of generated door components and distance-driven gait art."""
from pathlib import Path
import struct
from PIL import Image
from pack_guest_cast import isolate
ROOT = Path(__file__).resolve().parents[2]
ART = ROOT / "assets/sprites/hall"
OUT = ROOT / "assets/animations/hall"
PREVIEWS = ROOT / "build/hall-expansion"
def pack(name, frames):
w, h = frames[0].size
assert all(frame.size == (w, h) for frame in frames)
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 guests():
sheet = Image.open(ART / "guest-walk-v2.png").convert("RGBA")
# The generated strip has variable row heights; these gutters separate feet/heads.
rows = (0, 190, 376, 560, 746, 916, sheet.height)
frames = []
for appearance in range(6):
cells = [isolate(sheet.crop((col*sheet.width//8, rows[appearance],
(col+1)*sheet.width//8, rows[appearance+1]))) for col in range(8)]
scale = min(126/max(cell.width for cell in cells), 204/max(cell.height for cell in cells))
for cell in cells:
cell = cell.resize((round(cell.width*scale), round(cell.height*scale)), Image.Resampling.LANCZOS)
frame = Image.new("RGBA", (128,224))
frame.alpha_composite(cell, ((128-cell.width)//2, 224-cell.height))
frames.append(frame)
pack("guest-walk-v2.bin", frames)
pack("guest-walk-left-v2.bin", [frame.transpose(Image.Transpose.FLIP_LEFT_RIGHT) for frame in frames])
preview = Image.new("RGBA", (8*128,6*224), (27,30,29,255))
for i, frame in enumerate(frames[:48]):
preview.alpha_composite(frame, ((i%8)*128, (i//8)*224))
preview.save(PREVIEWS / "guest-walk-v2-preview.png")
def door():
sheet = Image.open(ART / "basement-door-v2.png").convert("RGBA")
leaf = isolate(sheet.crop((0,0,640,sheet.height))).resize((256,512), Image.Resampling.LANCZOS)
source_frame = isolate(sheet.crop((640,0,1420,sheet.height)))
wheel = isolate(sheet.crop((1420,0,sheet.width,sheet.height))).resize((92,92), Image.Resampling.LANCZOS)
frames = []
for phase in range(17):
texture = leaf.copy()
rotated = wheel.rotate(-270*phase/16, Image.Resampling.BICUBIC, expand=False)
# UV texels are not square in world space on the 1.5 by 1.9 metre leaf.
rotated = rotated.resize((58,92), Image.Resampling.LANCZOS)
texture.alpha_composite(rotated, (99,244))
frames.append(texture)
# Map the rigid jambs around a known rectangular aperture, without a baked leaf.
w,h = source_frame.size
left, right, lintel = round(w*.24), round(w*.72), round(h*.14)
frame = Image.new("RGBA", (256,512))
frame.alpha_composite(source_frame.crop((0,0,left,h)).resize((64,512)), (0,0))
frame.alpha_composite(source_frame.crop((right,0,w,h)).resize((64,512)), (192,0))
frame.alpha_composite(source_frame.crop((left,0,right,lintel)).resize((128,32)), (64,0))
frames.append(frame)
pack("basement-door-v2.bin", frames)
preview = Image.new("RGBA", (3*256,512), (27,30,29,255))
for i, texture in enumerate((frames[0],frames[8],frame)):
preview.alpha_composite(texture,(i*256,0))
preview.save(PREVIEWS / "basement-door-v2-preview.png")
if __name__ == "__main__":
PREVIEWS.mkdir(parents=True, exist_ok=True)
guests()
door()
print("Packed 96 gait frames and 18 door textures; runtime remains native assembly.")