- 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
62 lines
2.3 KiB
Python
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()
|