extends Node ## Music / SFX / Voice (Piper lines under assets/audio/voice/{lang}/{dialog_key}.wav). signal burger_cham_triggered const BURGER_INTERVAL := 120.0 const MUSIC_TITLE := "res://assets/audio/placeholders/music_title.wav" const SFX_EXPLOSION := "res://assets/audio/placeholders/sfx_explosion.wav" const SFX_HIT := "res://assets/audio/placeholders/sfx_hit.wav" const SFX_BURGER := "res://assets/audio/placeholders/sfx_burger.wav" ## Fallback language-picker clips if keyed Piper files missing const VOICE_DE := "res://assets/audio/placeholders/voice_de.wav" const VOICE_EN := "res://assets/audio/placeholders/voice_en.wav" const VOICE_BY := "res://assets/audio/placeholders/voice_by.wav" var _music: AudioStreamPlayer var _sfx: AudioStreamPlayer var _voice: AudioStreamPlayer var _burger_timer: float = 0.0 var burger_cham_enabled: bool = true var _last_voice_length: float = 0.0 func _ready() -> void: process_mode = Node.PROCESS_MODE_ALWAYS _music = AudioStreamPlayer.new() _music.bus = "Music" _music.volume_db = 12.0 # intentionally painfully loud add_child(_music) _sfx = AudioStreamPlayer.new() _sfx.bus = "SFX" add_child(_sfx) _voice = AudioStreamPlayer.new() _voice.bus = "Voice" add_child(_voice) func _process(delta: float) -> void: if not burger_cham_enabled: return if GameState.current_scene_id != "title_screen": return _burger_timer += delta if _burger_timer >= BURGER_INTERVAL: _burger_timer = 0.0 trigger_burger_cham() func play_title_music() -> void: var stream := _load_stream(MUSIC_TITLE) if stream == null: return _music.stream = stream _music.volume_db = 12.0 if not _music.playing: _music.play() func stop_music() -> void: _music.stop() func play_sfx(path: String) -> void: var stream := _load_stream(path) if stream == null: return _sfx.stream = stream _sfx.play() func play_explosion() -> void: play_sfx(SFX_EXPLOSION) func play_hit() -> void: play_sfx(SFX_HIT) func play_language_voice(lang: String) -> void: var key := "lang_voice_%s" % lang if play_dialog_line(key) > 0.0: return var path := VOICE_DE match lang: "en": path = VOICE_EN "by": path = VOICE_BY var stream := _load_stream(path) if stream == null: return _voice.stream = stream _voice.play() _last_voice_length = _stream_length(stream) ## Plays Piper line for locale key. Returns clip length in seconds (0 if missing). func play_dialog_line(key: String) -> float: if key == "": return 0.0 if not (key.begins_with("dialog_") or key.begins_with("lang_voice_")): return 0.0 var lang := "de" if Settings: lang = Settings.language var path := "res://assets/audio/voice/%s/%s.wav" % [lang, key] var stream := _load_stream(path) if stream == null and lang != "de": path = "res://assets/audio/voice/de/%s.wav" % key stream = _load_stream(path) if stream == null: _last_voice_length = 0.0 return 0.0 _voice.stop() _voice.stream = stream _voice.play() _last_voice_length = _stream_length(stream) return _last_voice_length func last_voice_length() -> float: return _last_voice_length func stop_voice() -> void: _voice.stop() func trigger_burger_cham() -> void: play_sfx(SFX_BURGER) burger_cham_triggered.emit() func _stream_length(stream: AudioStream) -> float: if stream == null: return 0.0 if stream.has_method("get_length"): return float(stream.get_length()) return 0.0 func _load_stream(path: String) -> AudioStream: if ResourceLoader.exists(path): return load(path) return null