KAZIK/tools/generators/pack_scratch_polish.py
Игорь Федосеев ca62db53e9 feat: implement native billiards, scratch tickets, first-person dice, and city wake scene
- Billiards: add native Russian Pyramid & American Eight-Ball pool with physics prediction, controls, and regression tests
- Scratch Tickets: add dedicated counter, mat presentation, elliptical coin scraping, 4 ticket types, and payout effects
- First-Person Dice: add native dice scene, hand anchor frames, throwing animation, and table perspective
- City Wake & Weather: add arrival cutscene, alley geometry, weather/rain effects, and lighter/rocker animation
- World Dressing: add hall paintings, furniture packs, hall lighting, and scene regression tests
- Build & Tooling: update build.bat for modern MSVC toolchain, update launcher hash, and refresh shipping binaries
2026-10-16 12:37:40 +03:00

62 lines
2.3 KiB
Python

"""Offline normalization and sound design; none of this runs inside the game."""
from pathlib import Path
import math
import random
import struct
import wave
from PIL import Image
ROOT = Path(__file__).resolve().parents[2]
def sound(name,seconds,style):
rate = 22050
rng = random.Random(713+style)
samples = []
previous = 0.0
low = 0.0
for i in range(int(rate*seconds)):
t = i/rate
noise = rng.uniform(-1,1)
low = low*.72+noise*.28
high = noise-previous
previous = noise
envelope = min(1,t/.012)*min(1,(seconds-t)/.04)
if style==0:
value = (high*.16+low*.22)*(0.65+0.35*math.sin(t*79)**2)
elif style==1:
value = low*.22+high*.08
envelope *= math.sin(math.pi*t/seconds)**.7
else:
value = 0.0
for offset in (0,.065,.135,.23):
age = t-offset
if age>=0:
value += (math.sin(age*math.tau*1760)+math.sin(age*math.tau*2410)*.4)*math.exp(-age*34)*.15
value += high*.04*math.exp(-t*12)
samples.append(max(-32767,min(32767,round(value*envelope*20000))))
path = ROOT/"assets/audio/generated"/name
with wave.open(str(path),"wb") as out:
out.setparams((1,2,rate,0,"NONE","not compressed"))
out.writeframes(struct.pack(f"<{len(samples)}h",*samples))
def main():
image = Image.open(ROOT/"assets/sprites/scratch/payout-chips-v1.png").convert("RGBA")
alpha = image.getchannel("A").point(lambda a:255 if a>=32 else 0)
image = image.crop(alpha.getbbox())
image.thumbnail((96,72),Image.Resampling.LANCZOS)
canvas = Image.new("RGBA",(96,72))
canvas.paste(image,((96-image.width)//2,72-image.height))
canvas.putalpha(canvas.getchannel("A").point(lambda a:255 if a>=128 else 0))
raw = canvas.tobytes("raw","BGRA")
(ROOT/"assets/animations/scratch/payout-chips-v1.bin").write_bytes(struct.pack("<4s4I",b"ANM1",96,72,1,len(raw))+raw)
canvas.save(ROOT/"build/scratch/payout-chips-packed.png")
sound("scratch-scrape.wav",.155,0)
sound("scratch-paper.wav",.42,1)
sound("scratch-payout.wav",.48,2)
print("Packed grounded 96x72 payout sprite and three short original scratch/paper/payout sounds")
if __name__=="__main__":
main()