"""Compile custom Hell Tower assets and menu art into engine-ready binary packs.""" import os import struct from pathlib import Path from PIL import Image, ImageOps ROOT = Path(__file__).resolve().parents[2] HELL_DIR = ROOT / "assets/hell_tower" def write_anm1_pack(dst_path: Path, frames: list): dst_path.parent.mkdir(parents=True, exist_ok=True) width, height = frames[0].size if not (0 < len(frames) <= 64) or not all(f.size == (width, height) for f in frames): raise ValueError(f"Invalid frames for {dst_path.name}") raw_bgra = b"".join(f.tobytes("raw", "BGRA") for f in frames) header = struct.pack("<4sIIII", b"ANM1", width, height, len(frames), len(raw_bgra)) dst_path.write_bytes(header + raw_bgra) print(f"[OK] Wrote ANM1 pack: {dst_path} ({len(frames)} frames, {width}x{height}, {len(header + raw_bgra)} bytes)") def write_cta1_pack(dst_path: Path, image: Image.Image): dst_path.parent.mkdir(parents=True, exist_ok=True) image = image.convert("RGBA").resize((640, 480), Image.Resampling.LANCZOS) raw_bgra = image.tobytes("raw", "BGRA") header = struct.pack("<4sIII", b"CTA1", 640, 480, len(raw_bgra)) dst_path.write_bytes(header + raw_bgra) print(f"[OK] Wrote CTA1 pack: {dst_path} (640x480, {len(header + raw_bgra)} bytes)") def slice_sprite_sheet(path: Path, cols: int, rows: int, cell_size: tuple, props: bool = False): source = Image.open(path).convert("RGBA") cells = [] for row in range(rows): for col in range(cols): cell = source.crop(( col * source.width // cols, row * source.height // rows, (col + 1) * source.width // cols, (row + 1) * source.height // rows )) cell.putalpha(cell.getchannel("A").point(lambda a: 255 if a >= 128 else 0)) bounds = cell.getchannel("A").getbbox() if bounds is None: bounds = (0, 0, cell.width, cell.height) cells.append(cell.crop(bounds)) factor = min((cell_size[0] - 12) / max(c.width for c in cells), (cell_size[1] - 12) / max(c.height for c in cells)) frames = [] for cell in cells: scale = min((cell_size[0] - 12) / cell.width, (cell_size[1] - 12) / cell.height) if props else factor cell = cell.resize((round(cell.width * scale), round(cell.height * scale)), Image.Resampling.NEAREST) frame = Image.new("RGBA", cell_size) y = (cell_size[1] - cell.height) // 2 if props else cell_size[1] - 6 - cell.height frame.alpha_composite(cell, ((cell_size[0] - cell.width) // 2, y)) frames.append(frame) return frames def pack_all(): print("Packing Hell Tower assets...") # 1. Hell Tower Menu background menu_candidates = [ HELL_DIR / "environment/HellTower_ menu.jpg", HELL_DIR / "environment/HellTower_menu.jpg", HELL_DIR / "environment/HellTower_ menu.png", HELL_DIR / "environment/HellTower_menu.png" ] menu_src = None for mc in menu_candidates: if mc.exists(): menu_src = mc break if menu_src: menu_im = Image.open(menu_src).convert("RGBA").resize((640, 480), Image.Resampling.LANCZOS) write_anm1_pack(ROOT / "assets/animations/hall/helltower-menu.bin", [menu_im]) else: print("[WARN] HellTower_menu image not found!") # 2. Coin table hell coin_src = HELL_DIR / "01_coin/table-v1-hell.jpg" if not coin_src.exists(): coin_src = HELL_DIR / "01_coin/table-v1-hell.png" if coin_src.exists(): bg = Image.open(coin_src).convert("RGBA").resize((640, 480), Image.Resampling.NEAREST) framed = Image.new("RGBA", (640, 480), (20, 25, 29, 255)) framed.paste(bg.crop((0, 0, 640, 440)), (0, 40)) framed.putalpha(255) write_anm1_pack(ROOT / "assets/animations/coin/table-v1-hell.bin", [framed]) # 3. Card table hell (Blackjack, Poker, Durak) card_src = HELL_DIR / "04_05_08_card_tables/card-table-pixel-v3-hell.jpg" if not card_src.exists(): card_src = HELL_DIR / "04_05_08_card_tables/card-table-pixel-v3-hell.png" if card_src.exists(): write_cta1_pack(ROOT / "assets/textures/card-table-hell.bin", Image.open(card_src)) # 4. Wheel closeup & table hell wheel_closeup = HELL_DIR / "07_casino_roulette/closeup-v1-hell.jpg" if not wheel_closeup.exists(): wheel_closeup = HELL_DIR / "07_casino_roulette/closeup-v1-hell.png" if wheel_closeup.exists(): w_img = Image.open(wheel_closeup).convert("RGBA").resize((640, 480), Image.Resampling.NEAREST) w_img.putalpha(255) write_anm1_pack(ROOT / "assets/animations/wheel/closeup-v1-hell.bin", [w_img]) wheel_table = HELL_DIR / "07_casino_roulette/table-v1-hell.jpg" if not wheel_table.exists(): wheel_table = HELL_DIR / "07_casino_roulette/table-v1-hell.png" if wheel_table.exists(): w_img = Image.open(wheel_table).convert("RGBA").resize((640, 480), Image.Resampling.NEAREST) w_img.putalpha(255) write_anm1_pack(ROOT / "assets/animations/wheel/table-v1-hell.bin", [w_img]) # 5. Buckshot (and Russian Roulette) assets # Room bs_room = HELL_DIR / "10_buckshot/room-v2-hell.jpg" if not bs_room.exists(): bs_room = HELL_DIR / "10_buckshot/room-v2-hell.png" if bs_room.exists(): room_im = ImageOps.fit(Image.open(bs_room).convert("RGBA"), (640, 480), method=Image.Resampling.NEAREST) write_anm1_pack(ROOT / "assets/animations/buckshot/room-v2-hell.bin", [room_im]) # Dealer base bs_dealer = HELL_DIR / "10_buckshot/dealer-source-v2-hell.png" if bs_dealer.exists(): frames = slice_sprite_sheet(bs_dealer, 4, 4, (256, 224)) write_anm1_pack(ROOT / "assets/animations/buckshot/dealer-v2-hell.bin", frames) # Dealer actions bs_actions = HELL_DIR / "10_buckshot/dealer-actions-v3-hell.png" if bs_actions.exists(): frames = slice_sprite_sheet(bs_actions, 4, 4, (256, 224)) write_anm1_pack(ROOT / "assets/animations/buckshot/dealer-actions-v3-hell.bin", frames) # Death bs_death = HELL_DIR / "10_buckshot/death-v3-hell.png" if bs_death.exists(): frames = slice_sprite_sheet(bs_death, 4, 3, (256, 224)) write_anm1_pack(ROOT / "assets/animations/buckshot/death-v3-hell.bin", frames) print("All Hell Tower asset packs generated successfully!") if __name__ == "__main__": pack_all()