- 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
68 lines
2.8 KiB
Python
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()
|