ShitboxInfinity/src/sfx.gd
m3ultra fd1218621b The Garage: shit-tier upgrade economy
Earn bucks from every mode (takedowns, outruns, escapes, derby kills, medals,
a cut of crash damage) and spend them on performance parts of deeply
questionable provenance: K-MART MEGA WING, GO-FASTER STRIPES, SERVO FLAME KIT,
BLINGY MAGS, SERVO NEONS, BUNNINGS DRAINPIPE, TURBO THRU THE BONNET. Every
part gives a real stat bump AND bolts a procedural eyesore onto the car, sized
off the model AABB so the same tat fits a BUBBLA or a Kingswood. Fully kitted:
+35% power, +1.1 grip, +0.55 boost.

Per-car, persistent (user://garage.save), player-only -- stats apply to a
DUPLICATE of the shared CarStats so tuning your Lazza doesn't secretly tune
every Lazza in the city, and the save path is redirectable so the smoke test
can never wipe real progression. Bolt-ons attach after the deformer snapshot:
the wing survives every crash, as is right and proper.

GARAGE button + bucks readout in the menu; shop screen with prices, blurbs and
FITTED states. Smoke covers the earn/buy/duplicate-stats roundtrip.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 10:50:55 +10:00

67 lines
1.9 KiB
GDScript

class_name Sfx
## Tiny audio utility over the procedural kit in assets/sounds (tools/gen_sounds.py).
## Loops are flagged here rather than in per-file .import settings so a wav
## regenerated by the tool never silently loses its loop flag.
const DIR := "res://assets/sounds/%s.wav"
const LOOPS := ["engine", "skid", "boost", "siren"]
static var _cache := {}
static func stream(name: String) -> AudioStream:
if _cache.has(name):
return _cache[name]
var path := DIR % name
if not ResourceLoader.exists(path):
return null
var s: AudioStream = load(path)
if s is AudioStreamWAV and name in LOOPS:
var w := s as AudioStreamWAV
w.loop_mode = AudioStreamWAV.LOOP_FORWARD
w.loop_begin = 0
w.loop_end = w.data.size() / 2 # 16-bit mono: 2 bytes per frame
_cache[name] = s
return s
static func looper(parent: Node, name: String, db: float) -> AudioStreamPlayer3D:
## Persistent 3D player for a looped bed (engine, skid, siren). Not playing
## until the caller decides; returns null when the kit isn't generated yet.
var s := stream(name)
if s == null:
return null
var p := AudioStreamPlayer3D.new()
p.stream = s
p.volume_db = db
p.max_distance = 90.0
parent.add_child(p)
return p
static func shot_2d(name: String, db := -4.0) -> void:
## UI one-shot: no position, plays on the scene tree root.
var s := stream(name)
if s == null:
return
var tree := Engine.get_main_loop() as SceneTree
if tree == null:
return
var p := AudioStreamPlayer.new()
p.stream = s
p.volume_db = db
tree.root.add_child(p)
p.play()
p.finished.connect(p.queue_free)
static func shot(parent: Node, name: String, db := 0.0, pitch := 1.0) -> void:
## Fire-and-forget one-shot at the parent's position.
var s := stream(name)
if s == null:
return
var p := AudioStreamPlayer3D.new()
p.stream = s
p.volume_db = db
p.pitch_scale = pitch
p.max_distance = 160.0
parent.add_child(p)
p.play()
p.finished.connect(p.queue_free)