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

74 lines
3.4 KiB
Python

"""Register generated motion strips to rigid arch/pedestal anchors."""
from pathlib import Path
import struct
from PIL import Image, ImageDraw
from pack_guest_cast import isolate
ROOT = Path(__file__).resolve().parents[2]
def cells(name, columns, rows):
sheet = Image.open(ROOT/f"assets/sprites/hall/{name}.png").convert("RGBA")
return [sheet.crop((c*sheet.width//columns,r*sheet.height//rows,
(c+1)*sheet.width//columns,(r+1)*sheet.height//rows))
for r in range(rows) for c in range(columns)]
def write(name, frames):
w,h = frames[0].size
raw = b"".join(frame.tobytes("raw","BGRA") for frame in frames)
(ROOT/f"assets/animations/hall/{name}.bin").write_bytes(
struct.pack("<4s4I",b"ANM1",w,h,len(frames),len(raw))+raw)
def anchored(source, width, height):
bounds = [cell.getchannel("A").point(lambda a: 255 if a >=128 else 0).getbbox()
for cell in source]
box = min(b[0] for b in bounds),min(b[1] for b in bounds),max(b[2] for b in bounds),max(b[3] for b in bounds)
scale = min((width-12)/(box[2]-box[0]),(height-12)/(box[3]-box[1]))
size = round((box[2]-box[0])*scale),round((box[3]-box[1])*scale)
result = []
for cell in source:
sprite = cell.crop(box).resize(size,Image.Resampling.LANCZOS)
sprite.putalpha(sprite.getchannel("A").point(lambda a: 255 if a >=128 else 0))
frame = Image.new("RGBA",(width,height))
frame.alpha_composite(sprite,((width-size[0])//2,height-6-size[1]))
result.append(frame)
return result
def main():
source = [isolate(cell) for cell in cells("upper-portal-v3",3,4)]
active = []
for cell in source:
sprite = cell.resize((244,308),Image.Resampling.LANCZOS)
sprite.putalpha(sprite.getchannel("A").point(lambda a: 255 if a >=128 else 0))
frame = Image.new("RGBA",(256,320))
frame.alpha_composite(sprite,(6,6))
active.append(frame)
# Keep every rigid arch pixel identical; register only the generated vortex.
aperture = Image.new("L",(256,320))
ImageDraw.Draw(aperture).polygon([(77,295),(77,145),(82,115),(99,94),
(128,73),(157,94),(174,115),(179,145),(179,295)],fill=255)
locked = [Image.composite(frame,active[0],aperture) for frame in active]
old = (ROOT/"assets/animations/hall/upper-portal-v2.bin").read_bytes()
inactive = Image.frombytes("RGBA",(224,320),old[20:20+224*320*4],"raw","BGRA")
inactive = inactive.resize((256,320),Image.Resampling.LANCZOS)
inactive = Image.composite(inactive,active[0],aperture)
write("upper-portal-v3",[frame.resize((256,512),Image.Resampling.LANCZOS)
for frame in [inactive,*locked]])
levers = anchored(cells("upper-levers-v3",3,2),128,224)
write("upper-levers-v3",levers)
braziers = anchored(cells("upper-braziers-v3",2,2),128,256)
for frame in braziers[1:]:
frame.paste(braziers[0].crop((0,100,128,256)),(0,100))
write("upper-braziers-v3",braziers)
preview = Image.new("RGBA",(768,640),(20,22,23,255))
for i,frame in enumerate([inactive,locked[0],locked[5],*levers[:3]]):
preview.alpha_composite(frame,((i%3)*256,(i//3)*320))
preview.save(ROOT/"build/upper-motion-preview.png")
print("Packed 12 vortex phases plus inactive arch, six lever positions, four brazier phases")
if __name__ == "__main__":
main()