KAZIK/tools/generators/pack_european_wheel.py
Artvasil09 4f8ae0893b Implement native slots, coin toss, European roulette and hall furniture
Add native assembly scenes and assets for Slots (777), Coin flip, and European Roulette; add 3D hall furniture packs and prompts; improve Russian UI strings in CP1251; update launcher hash and rebuild shipping binaries.
2026-10-10 14:43:30 +03:00

78 lines
3.4 KiB
Python

"""Offline scene packing and exact 37-pocket rotor for the MASM roulette renderer."""
import json
import math
import argparse
from pathlib import Path
from PIL import Image, ImageDraw, ImageFont
import pack_arrival_assets as packs
ROOT = Path(__file__).resolve().parents[2]
ORDER = [0, 32, 15, 19, 4, 21, 2, 25, 17, 34, 6, 27, 13, 36, 11, 30,
8, 23, 10, 5, 24, 16, 33, 1, 20, 14, 31, 9, 22, 18, 29, 7, 28,
12, 35, 3, 26]
RED = {1, 3, 5, 7, 9, 12, 14, 16, 18, 19, 21, 23, 25, 27, 30, 32, 34, 36}
def rotor(numbered=True):
im = Image.new("RGBA", (512, 512))
pixels = im.load()
for y in range(512):
for x in range(512):
dx, dy = x - 256, y - 256
radius = math.hypot(dx, dy)
if radius > 240:
continue
noise = ((x * 193 + y * 37 + (x ^ y) * 13) % 17) - 8
base = (82, 41, 25)
if radius >= 236 or 181 <= radius < 185 or 225 <= radius < 229:
base = (199, 158, 74)
elif radius >= 185:
angle = math.atan2(dy, dx) % math.tau
slot = int((angle / math.tau * 37 + .5) % 37)
value = ORDER[slot]
base = (20, 95, 56) if value == 0 else (
(132, 32, 38) if value in RED else (25, 30, 28))
boundary = abs((angle / math.tau * 37 + .5) % 1 - .5)
if boundary > .465:
base = (211, 176, 94)
elif radius < 28:
base = (191, 145, 58) if radius > 22 else (234, 207, 137)
pixels[x, y] = tuple(max(0, min(255, c + noise)) for c in base) + (255,)
if not numbered:
return im
font = ImageFont.truetype("C:/Windows/Fonts/arialbd.ttf", 20)
for index, value in enumerate(ORDER):
angle = index * math.tau / 37
label = Image.new("RGBA", (44, 26))
draw = ImageDraw.Draw(label)
draw.text((22, 13), str(value), font=font, anchor="mm", fill=(247, 238, 211))
label = label.rotate(-index * 360 / 37 - 90, expand=True, resample=Image.Resampling.NEAREST)
cx, cy = round(256 + 208 * math.cos(angle)), round(256 + 208 * math.sin(angle))
im.alpha_composite(label, (cx - label.width // 2, cy - label.height // 2))
return im
def main(rotor_only=False):
packs.OUTPUT = ROOT / "assets/animations/wheel"
preview = ROOT / "build/wheel"
preview.mkdir(parents=True, exist_ok=True)
manifest = {"order": ORDER, "zero_pockets": 1}
for name in (() if rotor_only else ("table", "closeup")):
image = Image.open(ROOT / "assets/sprites/wheel" / (name + "-v1.png")).convert("RGBA")
image = image.resize((640, 480), Image.Resampling.NEAREST)
image.putalpha(255)
packs.write_pack(name + "-v1.bin", [image])
image.save(preview / (name + ".png"))
# Numerals now belong to the native screen-space renderer, not a warped image.
disk = rotor(numbered=False).resize((480, 480), Image.Resampling.NEAREST)
packs.write_pack("rotor-v2.bin", [disk])
disk.save(preview / "rotor-v2.png")
(packs.OUTPUT / "manifest-v1.json").write_text(json.dumps(manifest, indent=2) + "\n", encoding="ascii")
print("Packed the exact 37-pocket rotor; labels are rendered by MASM")
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--rotor-only", action="store_true")
main(parser.parse_args().rotor_only)