KAZIK/tools/generators/pack_darts.py
Artvasil09 23dc40ee5c feat: implement native darts mini-game, AI observer mode, and fortune session assets
- Darts: native 500-point match against Max with sway/fatigue mouse aim, parabolic throw camera, raster arm animations, and 16 camera-relative flying dart views

- AI Observer: autonomous spectator mode for Hold'em, Blackjack, Durak, and Billiards with profile isolation and playback controls

- Fortune Session: raster sprite packs, mist/orb animations, tarot divination spreads, and pack tools

- Hall Integration: wall dartboard in lounge, observer terminal dispatch, and bot turn hooks

- Packaging & Verification: updated installer streaming hash, synchronized launcher, passing check suites and release smoke checks
2026-10-24 22:10:25 +03:00

103 lines
4.3 KiB
Python

"""Exact clock-board geometry plus fixed-grid full-arm animation packing."""
from pathlib import Path
import math
import random
import struct
from PIL import Image, ImageDraw, ImageFont
ROOT = Path(__file__).resolve().parents[2]
ART = ROOT / "assets/sprites/darts"
OUT = ROOT / "assets/animations/darts"
SECTORS = [20, 1, 18, 4, 13, 6, 10, 15, 2, 17, 3, 19, 7, 16, 8, 11, 14, 9, 12, 5]
def pack(name, frames):
width, height = frames[0].size
payload = b"".join(frame.tobytes("raw", "BGRA") for frame in frames)
(OUT / name).write_bytes(struct.pack("<4s4I", b"ANM1", width, height, len(frames), len(payload)) + payload)
def board():
rng = random.Random(301)
image = Image.new("RGBA", (512, 512))
pixels = image.load()
for y in range(512):
for x in range(512):
dx, dy = x-256, 256-y
radius = math.hypot(dx, dy)
angle = math.atan2(dx, dy)
sector = int(((angle + math.pi/20) % math.tau) / (math.tau/20))
red = sector % 2 == 0
color = (30, 36, 39)
if radius < 170:
color = (35, 38, 37) if red else (214, 206, 178)
if 99 <= radius <= 107 or 162 <= radius:
color = (174, 37, 47) if red else (28, 112, 87)
if radius <= 15.9:
color = (28, 112, 87)
if radius <= 6.35:
color = (187, 37, 47)
elif radius < 210:
color = (20, 24, 25)
elif radius < 240:
color = (72, 29, 35)
elif radius < 248:
color = (31, 37, 41)
else:
continue
grain = rng.randrange(-11, 12) if radius < 170 else rng.randrange(-3, 4)
pixels[x, y] = tuple(max(0, min(255, c+grain)) for c in color) + (255,)
draw = ImageDraw.Draw(image)
wire = (171, 178, 173, 255)
for r in (6.35, 15.9, 99, 107, 162, 170):
draw.ellipse((256-r, 256-r, 256+r, 256+r), outline=wire, width=1)
for index in range(20):
theta = (index*18-9)*math.pi/180
draw.line([(256+math.sin(theta)*16, 256-math.cos(theta)*16),
(256+math.sin(theta)*170, 256-math.cos(theta)*170)], fill=wire)
font = ImageFont.truetype("C:/Windows/Fonts/arialbd.ttf", 23)
for index, value in enumerate(SECTORS):
theta = index*math.tau/20
draw.text((256+math.sin(theta)*190, 256-math.cos(theta)*190), str(value),
font=font, fill=(227, 230, 220), anchor="mm", stroke_width=1)
draw.ellipse((12, 12, 500, 500), outline=(104, 114, 120), width=3)
image.save(ART / "board-v1.png")
return image
def main():
ART.mkdir(parents=True, exist_ok=True)
OUT.mkdir(parents=True, exist_ok=True)
pack("board-v1.bin", [board()])
wall = Image.open(ART / "lounge-wall-v1.png").convert("RGBA")
pack("lounge-wall-v1.bin", [wall.resize((512,512), Image.Resampling.LANCZOS)])
anchors = []
for name in ("arm-throw-v3", "dart-views-v1"):
source = ART / (name + ".png")
sheet = Image.open(source).convert("RGBA")
assert sheet.width == sheet.height
cell = sheet.width / 4
frames = [sheet.crop(tuple(round(v*cell) for v in (x, y, x+1, y+1)))
.resize((256, 256), Image.Resampling.LANCZOS) for y in range(4) for x in range(4)]
assert all(f.getchannel("A").getextrema()[0] == 0 for f in frames)
assert len({f.tobytes() for f in frames}) == 16
pack(name + ".bin", frames)
if name == "dart-views-v1":
for frame in frames:
metal = [(x,y) for y in range(256) for x in range(256)
if (lambda p: p[3] > 220 and min(p[:3]) > 50
and max(p[:3])-min(p[:3]) < 60)(frame.getpixel((x,y)))]
tip_x = min(x for x,y in metal)
ys = sorted(y for x,y in metal if x <= tip_x+2)
anchors.append((tip_x, ys[len(ys)//2]))
(ROOT / "src_graphics/darts_art.inc").write_text(
"; Generated per-view needle anchors in the 256px source canvas.\n"
+ "dtDartTipX3D dd " + ",".join(str(p[0]) for p in anchors) + "\n"
+ "dtDartTipY3D dd " + ",".join(str(p[1]) for p in anchors) + "\n", encoding="ascii")
print("Packed exact 20-sector board; whole-arm animation uses one fixed canvas and scale.")
if __name__ == "__main__":
main()