"""Pack project-local source art into the MASM renderer's 128px BGRA slots. Python is an offline build tool; the shipped game has no Python dependency. Only transparent alpha is discarded. Black, white and gold detail is retained. """ from pathlib import Path from PIL import Image ROOT = Path(__file__).resolve().parents[1] SIZE = 128 SLOTS = 48 CHROMA = (255, 0, 255, 0) def cells(path, columns, rows): image = Image.open(path).convert("RGBA") return [image.crop((c * image.width // columns, r * image.height // rows, (c + 1) * image.width // columns, (r + 1) * image.height // rows)) for r in range(rows) for c in range(columns)] def sprite(cell): # Preserve each sheet's common anchor and scale, including internal margins. scale = min((SIZE - 8) / cell.width, (SIZE - 8) / cell.height) resized = cell.resize((round(cell.width * scale), round(cell.height * scale)), Image.Resampling.LANCZOS) out = Image.new("RGBA", (SIZE, SIZE), (0, 0, 0, 0)) out.alpha_composite(resized, ((SIZE - resized.width) // 2, SIZE - 4 - resized.height)) return out def pack(image, is_sprite=False): result = bytearray() for r, g, b, a in image.getdata(): if is_sprite and a < 128: r, g, b, a = CHROMA result.extend((b, g, r, 0)) return result def main(): raw = (ROOT / "assets/textures/casino3d.bin").read_bytes() assert len(raw) == SLOTS * 64 * 64 * 4 textures = [] for n in range(SLOTS): tile = Image.frombytes("RGBA", (64, 64), raw[n * 16384:(n + 1) * 16384], "raw", "BGRA") tile.putalpha(255) textures.append(tile.resize((SIZE, SIZE), Image.Resampling.NEAREST)) atlas = cells(ROOT / "assets/sprites/casino-atlas-v1.png", 4, 4) dancer = cells(ROOT / "assets/sprites/cabaret-loop-v1.png", 4, 3) sprites = {} for n, frame in enumerate(dancer): sprites[10 + n] = sprite(frame) for slot, cell in {24: 4, 25: 5, 26: 6, 30: 7, 31: 8, 32: 10, 34: 9, 35: 11, 36: 12, 37: 13, 38: 14, 39: 15}.items(): sprites[slot] = sprite(atlas[cell]) # The original ATM uses an explicit magenta chroma background. textures[33].putalpha(255) for slot, image in sprites.items(): textures[slot] = image data = b"".join(pack(image, n in sprites) for n, image in enumerate(textures)) assert len(data) == SLOTS * SIZE * SIZE * 4 target = ROOT / "assets/textures/casino3d-hd.bin" target.write_bytes(data) preview = Image.new("RGBA", (SIZE * 8, SIZE * 6), (24, 29, 32, 255)) for n, image in enumerate(textures): preview.alpha_composite(image, ((n % 8) * SIZE, (n // 8) * SIZE)) preview.convert("RGB").save(ROOT / "build/graphics/texture-atlas-preview.png") dancer_preview = [sprites[10 + n].resize((256, 256), Image.Resampling.NEAREST) for n in range(12)] dancer_preview[0].save(ROOT / "build/graphics/cabaret-loop-preview.gif", save_all=True, append_images=dancer_preview[1:], duration=100, loop=0, disposal=2) print(f"Packed {SLOTS} x {SIZE}x{SIZE}: {target}") if __name__ == "__main__": main()