KAZIK/tools/generators/pack_guest_cast.py
Artvasil09 797ee366dc feat: implement upper lounge, manual stairs, cabaret, and casino terminals
- Native player-controlled stairs, upper lounge bar, and Hell Tower infernal portal
- Cabaret stage with musical acts, audience seating, and interaction
- Dedicated cash/trophy wall terminals with native balance and achievement screens
- Projecting 3D wall lanterns with ray/plane intersection
- Six roaming guests with seated poses and card rank rendering updates
- Synchronized launcher hash and updated binaries
2026-10-18 21:36:12 +03:00

68 lines
2.8 KiB
Python

"""Offline conversion of generated guest art into the existing ANM1 format."""
from pathlib import Path
import struct
from PIL import Image
ROOT = Path(__file__).resolve().parents[2]
SOURCE = ROOT / "assets/sprites/hall/guest-cast-v1.png"
TARGET = ROOT / "assets/animations/hall/guest-cast-v1.bin"
def isolate(cell):
alpha = cell.getchannel("A")
pixels = alpha.load()
remaining = {(x,y) for y in range(cell.height) for x in range(cell.width)
if pixels[x,y] >= 48}
components = []
while remaining:
point = remaining.pop()
component, pending = {point}, [point]
while pending:
x,y = pending.pop()
for other in ((x-1,y),(x+1,y),(x,y-1),(x,y+1)):
if other in remaining:
remaining.remove(other)
component.add(other)
pending.append(other)
components.append(component)
largest = max(components,key=len)
mask = Image.new("L",cell.size)
for x,y in largest:
mask.putpixel((x,y),pixels[x,y])
cell.putalpha(mask)
return cell.crop(cell.getbbox())
def main():
sheet = Image.open(SOURCE).convert("RGBA")
frames = []
# The approved sheet contains idle, three gait poses and a seated row.
# Repeat the passing pose to close the four-phase walk without a jump.
for column in range(6):
boundaries = (0,277,535,795,1055,sheet.height)
cells = [isolate(sheet.crop((column*sheet.width//6, boundaries[row],
(column+1)*sheet.width//6, boundaries[row+1])))
for row in range(5)]
scale = min(120/max(cell.width for cell in cells[:4]),
204/max(cell.height for cell in cells[:4]))
for row in (0, 1, 2, 3, 2, 4):
cell = cells[row]
pose_scale = min(scale,124/cell.width,160/cell.height) if row == 4 else scale
size = (max(1, round(cell.width*pose_scale)), max(1, round(cell.height*pose_scale)))
cell = cell.resize(size, Image.Resampling.LANCZOS)
if column == 1 and row == 4:
cell = cell.transpose(Image.Transpose.FLIP_LEFT_RIGHT)
frame = Image.new("RGBA", (128, 224))
frame.alpha_composite(cell, ((128-size[0])//2, 220-size[1]))
frames.append(frame)
payload = b"".join(frame.tobytes("raw", "BGRA") for frame in frames)
TARGET.write_bytes(struct.pack("<4s4I", b"ANM1",128,224,len(frames),len(payload))+payload)
contact = Image.new("RGBA", (6*128,6*224), (24,28,29,255))
for index, frame in enumerate(frames):
contact.alpha_composite(frame, ((index//6)*128,(index%6)*224))
contact.save(ROOT / "build/guest-cast-preview.png")
print("Packed six guests, six frames each:", TARGET)
if __name__ == "__main__":
main()