Unveränderter Ausgangsstand inklusive Masterprompt und Konzeptbildern unter assets/. .godot/ und docs/screenshots/ bleiben laut .gitignore unversioniert; letztere sind unter ../backups gesichert. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
70 lines
2.3 KiB
GDScript
70 lines
2.3 KiB
GDScript
extends Node
|
|
## A bounded pool and wall-clock spacing keep 2x battles from flooding audio.
|
|
|
|
const CLIPS: Array[String] = ["place", "error", "stone", "bolt", "mortar", "ember", "ability", "wave", "boss", "settle", "leak", "victory", "defeat"]
|
|
var viewed_team: int = 0
|
|
var enabled: bool = true
|
|
var played: int = 0
|
|
var _streams: Dictionary = {}
|
|
var _players: Array[AudioStreamPlayer] = []
|
|
var _last: Dictionary = {}
|
|
var _last_combat: int = -1000
|
|
|
|
func _ready() -> void:
|
|
enabled = DisplayServer.get_name() != "headless"
|
|
for id: String in CLIPS:
|
|
_streams[id] = load("res://assets/audio/" + id + ".wav")
|
|
for index: int in range(12):
|
|
var player := AudioStreamPlayer.new()
|
|
add_child(player)
|
|
_players.append(player)
|
|
GameState.phase_changed.connect(_phase_changed)
|
|
|
|
func play(id: String, lane: int = -1, combat: bool = false, pitch: float = 1.0) -> bool:
|
|
if not enabled or Preferences.muted or Preferences.volume <= 0.0 or not _streams.has(id):
|
|
return false
|
|
if lane >= 0 and lane != viewed_team:
|
|
return false
|
|
if combat and (GameState.paused or GameState.phase != GameState.Phase.COMBAT):
|
|
return false
|
|
var now: int = Time.get_ticks_msec()
|
|
if now - int(_last.get(id, -2000)) < (110 if combat else 80):
|
|
return false
|
|
if combat and now - _last_combat < 65:
|
|
return false
|
|
# Reserve two channels for wave/result/UI cues.
|
|
for index: int in range(10 if combat else 12):
|
|
var player: AudioStreamPlayer = _players[index]
|
|
if player.playing:
|
|
continue
|
|
player.stream = _streams[id]
|
|
player.volume_db = -13.0 if combat else -5.0
|
|
player.pitch_scale = clampf(pitch, .85, 1.15)
|
|
player.play()
|
|
_last[id] = now
|
|
if combat:
|
|
_last_combat = now
|
|
played += 1
|
|
return true
|
|
return false
|
|
|
|
func stop_combat() -> void:
|
|
for player: AudioStreamPlayer in _players:
|
|
player.stop()
|
|
|
|
func quit_game(code: int = 0) -> void:
|
|
enabled = false
|
|
stop_combat()
|
|
# Let the audio mixer release playback references before the tree is freed.
|
|
await get_tree().create_timer(.12).timeout
|
|
get_tree().quit(code)
|
|
|
|
func _phase_changed(phase: int) -> void:
|
|
stop_combat()
|
|
match phase:
|
|
GameState.Phase.COMBAT:
|
|
play("boss" if GameState.wave in [5, 8] else "wave")
|
|
GameState.Phase.RESULTS:
|
|
play("settle")
|
|
GameState.Phase.GAME_OVER:
|
|
play("victory" if GameState.outcome == 0 else ("settle" if GameState.outcome == GameState.DRAW else "defeat"))
|