- 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
74 lines
3.4 KiB
Python
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()
|