From eb1615ab6ba141ab991b69e1697e77e83c637aab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Wisniewski?= Date: Mon, 31 Aug 2026 18:15:37 +0200 Subject: [PATCH] Restore landing fade and missing event sounds --- audio/Sfx.gd | 6 ++++++ audio/make_sfx.py | 23 +++++++++++++++++++++++ audio/sfx_dialog.wav | Bin 0 -> 21212 bytes audio/sfx_dialog.wav.import | 24 ++++++++++++++++++++++++ enemies/Enemy.gd | 1 + tests/test_menu_scene_boundaries.gd | 4 ++++ tests/test_sfx.gd | 24 +++++++++++++++++++++--- ui/NarrationBanner.gd | 1 + ui/menus/FormationMenu.tscn | 12 ------------ world/Main.tscn | 11 +++++++++++ 10 files changed, 91 insertions(+), 15 deletions(-) create mode 100644 audio/sfx_dialog.wav create mode 100644 audio/sfx_dialog.wav.import diff --git a/audio/Sfx.gd b/audio/Sfx.gd index 85dc951..18317b9 100644 --- a/audio/Sfx.gd +++ b/audio/Sfx.gd @@ -24,12 +24,16 @@ const VOICES: int = 12 const SHOT: String = "shot" const SHIP_DEATH: String = "ship_death" +const ENEMY_DEATH: String = "enemy_death" const BUILDING_DEATH: String = "building_death" +const DIALOG: String = "dialog" const SOUNDS: Dictionary = { SHOT: "res://audio/sfx_shot.wav", SHIP_DEATH: "res://audio/sfx_ship_death.wav", + ENEMY_DEATH: "res://audio/sfx_ship_death.wav", BUILDING_DEATH: "res://audio/sfx_building_death.wav", + DIALOG: "res://audio/sfx_dialog.wav", } # Per-sound level. A shot is fired constantly and a death happens once, so @@ -37,7 +41,9 @@ const SOUNDS: Dictionary = { const LEVELS: Dictionary = { SHOT: -26.0, SHIP_DEATH: -23.0, + ENEMY_DEATH: -23.0, BUILDING_DEATH: -23.0, + DIALOG: -19.0, } # Shots vary a little in pitch so a stream of them does not turn into one diff --git a/audio/make_sfx.py b/audio/make_sfx.py index 4b3934c..e9d0929 100644 --- a/audio/make_sfx.py +++ b/audio/make_sfx.py @@ -125,6 +125,28 @@ def shot(seconds=0.22): return [_saturate(v, 2.0) * _attack(i, 0.0015) for i, v in enumerate(mixed)] +def dialog(seconds=0.24): + """A short radio handshake when a transmission line appears. + + Two rising digital tones establish the signal; a small static tail keeps + it in the same physical world as the scan-line presentation without + turning every revealed character into a click.""" + n = int(RATE * seconds) + tone = [0.0] * n + for start, end, level, delay in ((520.0, 820.0, 0.55, 0.0), + (940.0, 1380.0, 0.32, 0.045)): + begin = int(RATE * delay) + part = _sweep(n - begin, start, end, 0.8) + for i, value in enumerate(part): + at = begin + i + tone[at] += value * level * _decay(i, len(part), 2.8) + static = [value * _decay(i, n, 2.1) * 0.16 + for i, value in enumerate(_noise(n, 17, lambda t: 0.55 - 0.42 * t))] + mixed = _mix(tone, static) + return [_saturate(value, 1.8) * _attack(i, 0.002) + for i, value in enumerate(mixed)] + + def explosion(seconds, sub_from, sub_to, roar_seed, debris_level, drive): """A blast in three layers on three clocks: the crack is over in a fifth of a second, the roar runs most of the length, and the debris @@ -163,6 +185,7 @@ def explosion(seconds, sub_from, sub_to, roar_seed, debris_level, drive): if __name__ == "__main__": _write("sfx_shot", shot()) + _write("sfx_dialog", dialog()) # A vessel coming apart: bright, fast, with real low end under it. _write("sfx_ship_death", explosion(1.45, 260.0, 38.0, 5, 0.16, 2.6)) # A structure: longer, deeper, and it keeps coming down afterwards. diff --git a/audio/sfx_dialog.wav b/audio/sfx_dialog.wav new file mode 100644 index 0000000000000000000000000000000000000000..589b2636e62ca291e926eadbb8935eba5d43a988 GIT binary patch literal 21212 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 I|CgNq0LIoA!T void: hp -= amount if hp <= 0: EnemyExplosionVFX.spawn(get_tree().root, position) + Sfx.play(get_tree(), Sfx.ENEMY_DEATH) destroyed.emit() queue_free() diff --git a/tests/test_menu_scene_boundaries.gd b/tests/test_menu_scene_boundaries.gd index d825ec2..6dfb496 100644 --- a/tests/test_menu_scene_boundaries.gd +++ b/tests/test_menu_scene_boundaries.gd @@ -16,6 +16,8 @@ func _verify() -> void: return _fail("Main.tscn did not load") var main: Node = packed.instantiate() root.add_child(main) + if main.get_node_or_null("FadeOverlay/FadeRect") == null: + return _fail("Main lost the atmosphere fade overlay") for menu_name in MENUS: var menu: Node = main.get_node_or_null("UI/%s" % menu_name) if menu == null: @@ -26,6 +28,8 @@ func _verify() -> void: % [menu_name, expected]) if menu.get_script() == null: return _fail("%s has no owning script" % menu_name) + if menu.get_node_or_null("FadeOverlay") != null: + return _fail("%s incorrectly owns Main's atmosphere fade overlay" % menu_name) var source: String = FileAccess.get_file_as_string( "res://controllers/BuildController.gd") diff --git a/tests/test_sfx.gd b/tests/test_sfx.gd index f80487b..e69e254 100644 --- a/tests/test_sfx.gd +++ b/tests/test_sfx.gd @@ -2,8 +2,9 @@ extends SceneTree # Headless test: run with # godot --headless -s res://tests/test_sfx.gd -# Forgejo #146: a shot, a vessel dying and a structure coming down should be -# heard. +# Forgejo #146: shots and destruction should be heard. Enemy destruction and +# incoming narration are covered too, so visual events cannot silently miss +# the shared sound pool. # # The interesting failures here are not "no sound came out" - they are the # ones a play-through would not reveal for weeks. A voice pool that runs dry @@ -16,7 +17,7 @@ extends SceneTree # B. firing plays a shot, and it survives a fleet firing together - no # voice is lost, none is left stuck # C. a dying ship is heard, from a node that is freed on the same frame -# D. a destroyed structure is heard +# D. enemy and structure destruction are heard # E. levels are in proportion: a shot fires constantly and a death happens # once, so a shot must be the quieter of the two # F. sound keeps working while a dialog has the game stopped (#128) @@ -116,8 +117,12 @@ func _process(delta: float) -> bool: % [Sfx.VOICES * 3, Sfx.VOICES, busy]) # --- F. and a dialog does not silence it ----------------- + for voice in _pool().get_children(): + (voice as AudioStreamPlayer).stop() var narration: Control = scene.get_node("UI/NarrationBanner") narration.say("Everything stops but this.", "TEST") + if _sounding() == 0: + return _fail("a narration line appeared in silence") if not paused: return _fail("the dialog did not stop the game, so this proves nothing") for voice in _pool().get_children(): @@ -149,6 +154,19 @@ func _process(delta: float) -> bool: return _fail("a vessel died in silence") print("a vessel freed on the frame it dies is still heard") + # An enemy uses the same persistent pool. Its own node is freed on + # this frame too, so the call must happen before queue_free(). + for voice in _pool().get_children(): + (voice as AudioStreamPlayer).stop() + var enemy: Node2D = load("res://enemies/Enemy.tscn").instantiate() + scene.add_child(enemy) + enemy.take_damage(enemy.hp) + if not enemy.is_queued_for_deletion(): + return _fail("the enemy was not destroyed") + if _sounding() == 0: + return _fail("an enemy vessel exploded in silence") + print("an enemy vessel explosion is heard") + # --- D. a structure ------------------------------------ for voice in _pool().get_children(): (voice as AudioStreamPlayer).stop() diff --git a/ui/NarrationBanner.gd b/ui/NarrationBanner.gd index 24dfc19..6ee6354 100644 --- a/ui/NarrationBanner.gd +++ b/ui/NarrationBanner.gd @@ -238,6 +238,7 @@ func _advance() -> void: label.visible_characters = 0 speaker_label.text = speaker visible = true + Sfx.play(get_tree(), Sfx.DIALOG) _layout() queue_redraw() _sync_pause() diff --git a/ui/menus/FormationMenu.tscn b/ui/menus/FormationMenu.tscn index b8891d1..96f9ced 100644 --- a/ui/menus/FormationMenu.tscn +++ b/ui/menus/FormationMenu.tscn @@ -76,15 +76,3 @@ offset_top = 24 offset_bottom = 172 script = ExtResource("10") icon = 1 - -[node name="FadeOverlay" type="CanvasLayer" parent="."] -layer = 10 - -[node name="FadeRect" type="ColorRect" parent="FadeOverlay"] -mouse_filter = 2 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -color = Color(0, 0, 0, 0) diff --git a/world/Main.tscn b/world/Main.tscn index e859eda..63bd37d 100644 --- a/world/Main.tscn +++ b/world/Main.tscn @@ -493,3 +493,14 @@ offset_top = 300.0 offset_bottom = 428.0 script = ExtResource("36") +[node name="FadeOverlay" type="CanvasLayer" parent="."] +layer = 10 + +[node name="FadeRect" type="ColorRect" parent="FadeOverlay"] +mouse_filter = 2 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +color = Color(0, 0, 0, 0)