KAZIK/tools/generators/pack_hell_tower_assets.py
Игорь Федосеев eef2039c7e feat(hell_tower): integrate custom hell sprites, redesign tower menu, and fix entrance crash
- Add ResolveHellAssetPath to automatically load *-hell variants during tournamentMode with fallback to vanilla assets
- Rebuild Hell Tower briefing menu using HellTower_menu background with smooth floor cursor and sliding callout info card
- Fix buffer overrun in RenderHellTower3D by packing helltower-menu at native 640x480 resolution with bounds-checked blit
- Temporarily disable tower background music per user request
- Add asset packing pipeline, verification tools, and automated 10-floor run-through smoke test
2026-10-26 19:44:06 +03:00

148 lines
6.4 KiB
Python

"""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()