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