diff --git a/scripts/scenes/battle_scene.gd b/scripts/scenes/battle_scene.gd index d6180cd..c16b1b8 100644 --- a/scripts/scenes/battle_scene.gd +++ b/scripts/scenes/battle_scene.gd @@ -95,8 +95,8 @@ const UI_SFX_VOLUME_DB := -4.0 const DEFAULT_AUDIO_VOLUME_PERCENT := 75 const AUDIO_MUTED_DB := -80.0 const MAX_RECENT_SFX_KEYS := 32 -const AUDIO_PREVIEW_SFX_KEYS := ["menu_confirm", "footstep", "slash"] -const AUDIO_PREVIEW_LABEL := "확인음 · 발소리 · 타격음" +const AUDIO_PREVIEW_SFX_KEYS := ["menu_confirm", "footstep", "slash", "defeat_sting", "item_pickup"] +const AUDIO_PREVIEW_LABEL := "확인음 · 발소리 · 타격음 · 퇴각음 · 보급음" const FLOATING_TEXT_LIFETIME := 1.0 const FLOATING_TEXT_RISE := 42.0 const OBJECTIVE_NOTICE_DURATION := 2.6 @@ -3578,9 +3578,9 @@ func _create_hud() -> void: settings_bgm_volume_row.add_child(settings_bgm_volume_value_label) settings_sfx_toggle = CheckButton.new() - settings_sfx_toggle.text = "효과음: 선택·발소리·전투" + settings_sfx_toggle.text = "효과음: 선택·발소리·전투·보급" settings_sfx_toggle.custom_minimum_size = Vector2(374, 30) - settings_sfx_toggle.tooltip_text = "메뉴 선택음, 이동 발소리, 공격과 책략 효과음을 켜거나 끕니다." + settings_sfx_toggle.tooltip_text = "메뉴 선택음, 이동 발소리, 공격·책략·퇴각·보급 효과음을 켜거나 끕니다." settings_sfx_toggle.toggled.connect(_on_settings_sfx_toggled) settings_column.add_child(settings_sfx_toggle) @@ -3602,7 +3602,7 @@ func _create_hud() -> void: settings_sfx_volume_slider.step = 5.0 settings_sfx_volume_slider.size_flags_horizontal = Control.SIZE_EXPAND_FILL settings_sfx_volume_slider.custom_minimum_size = Vector2(194, 30) - settings_sfx_volume_slider.tooltip_text = "선택음과 전투 효과음 크기를 조절합니다." + settings_sfx_volume_slider.tooltip_text = "선택음, 이동음, 전투음, 보급음 크기를 조절합니다." settings_sfx_volume_slider.value_changed.connect(_on_settings_sfx_volume_changed) settings_sfx_volume_row.add_child(settings_sfx_volume_slider) @@ -3623,7 +3623,7 @@ func _create_hud() -> void: settings_audio_reset_button = Button.new() settings_audio_reset_button.text = "기본값 듣기" settings_audio_reset_button.custom_minimum_size = Vector2(374, 32) - settings_audio_reset_button.tooltip_text = "음악과 효과음을 켜고 75%로 되돌린 뒤 현재 음악, 확인음, 발소리, 타격음을 재생합니다." + settings_audio_reset_button.tooltip_text = "음악과 효과음을 켜고 75%로 되돌린 뒤 현재 음악, 확인음, 발소리, 타격음, 퇴각음, 보급음을 재생합니다." settings_audio_reset_button.pressed.connect(_on_settings_audio_reset_pressed) settings_column.add_child(settings_audio_reset_button) @@ -5248,10 +5248,10 @@ func _create_hud() -> void: title_bgm_volume_row.add_child(title_bgm_volume_value_label) title_sfx_toggle = CheckButton.new() - title_sfx_toggle.text = "효과음: 클릭·발소리·전투" + title_sfx_toggle.text = "효과음: 클릭·발소리·전투·보급" title_sfx_toggle.custom_minimum_size = Vector2(350, 30) title_sfx_toggle.button_pressed = title_sfx_enabled - title_sfx_toggle.tooltip_text = "메뉴 클릭음, 이동 발소리, 공격과 책략 효과음을 켜거나 끕니다." + title_sfx_toggle.tooltip_text = "메뉴 클릭음, 이동 발소리, 공격·책략·퇴각·보급 효과음을 켜거나 끕니다." title_sfx_toggle.toggled.connect(_on_title_sfx_toggled) title_settings_column.add_child(title_sfx_toggle) @@ -5298,7 +5298,7 @@ func _create_hud() -> void: "TitleAudioResetButton", "status", "기본값 듣기", - "음악과 효과음을 켜고 75%로 되돌린 뒤 현재 음악, 확인음, 발소리, 타격음을 재생합니다.", + "음악과 효과음을 켜고 75%로 되돌린 뒤 현재 음악, 확인음, 발소리, 타격음, 퇴각음, 보급음을 재생합니다.", Vector2(350, 32) ) title_audio_reset_button.pressed.connect(_on_title_audio_reset_pressed) @@ -5728,26 +5728,59 @@ func _play_ui_cancel() -> void: func _play_log_sfx(message: String) -> void: - if message.contains(" but misses.") or message.contains("헛쳤다"): + var normalized := message.strip_edges() + if normalized.is_empty(): + return + if normalized.contains(" but misses.") or normalized.contains("헛쳤다"): _play_sfx("guard_block") - elif message.begins_with("Objective updated:") or message.begins_with("Defeat condition updated:") or message.begins_with("군령 갱신:") or message.begins_with("패배 조건 갱신:"): + elif normalized.begins_with("Objective updated:") or normalized.begins_with("Defeat condition updated:") or normalized.begins_with("군령 갱신:") or normalized.begins_with("패배 조건 갱신:"): _play_ui_confirm() - elif message.ends_with(" withdraws from the battlefield.") or message.ends_with("전장을 물러났다."): - _play_ui_confirm() - elif message.contains(" attacks ") or message.contains(" counterattacks ") or message.contains(" 공격 ") or message.contains(" 반격 "): - if message.contains(" for ") or message.contains("피해"): + elif _log_message_is_unit_defeat(normalized): + _play_sfx("defeat_sting") + elif _log_message_is_supply(normalized): + _play_sfx("item_pickup") + elif _log_message_is_movement(normalized) or _log_message_is_reinforcement(normalized): + _play_sfx("footstep") + elif normalized.contains(" attacks ") or normalized.contains(" counterattacks ") or normalized.contains(" 공격 ") or normalized.contains(" 반격 "): + if normalized.contains(" for ") or normalized.contains("피해"): _play_sfx("hit_heavy") - elif message.contains(" casts ") or message.contains(" 시전 ") or message.contains("펼쳐"): - if message.contains("restoring") or message.contains(" until ") or message.contains("회복") or message.contains("지원"): + elif normalized.contains(" casts ") or normalized.contains(" 시전 ") or normalized.contains("펼쳐"): + if normalized.contains("restoring") or normalized.contains(" until ") or normalized.contains("회복") or normalized.contains("지원"): _play_sfx("skill_cast") else: _play_sfx("fire_skill") - elif message.contains(" uses ") or message.contains(" equips ") or message.contains(" 사용:") or message.contains("장착") or message.contains("해제") or message.contains(" 매입:") or message.contains(" 매각:") or message.contains("군자금") or message.begins_with("Received ") or message.begins_with("전리품:"): + elif normalized.contains(" uses ") or normalized.contains(" equips ") or normalized.contains(" 사용:") or normalized.contains("장착") or normalized.contains("해제") or normalized.contains(" 매입:") or normalized.contains(" 매각:"): _play_sfx("item_pickup") - elif message.contains("공적") or message.contains("품계") or message.contains("승급"): + elif normalized.contains("공적") or normalized.contains("품계") or normalized.contains("승급"): _play_sfx("menu_confirm") +func _log_message_is_unit_defeat(message: String) -> bool: + return ( + message.ends_with(" withdraws from the battlefield.") + or message.ends_with("전장을 물러났다.") + or message.ends_with(" 퇴각.") + or message.contains("으로 퇴각.") + ) + + +func _log_message_is_supply(message: String) -> bool: + if message.begins_with("Received ") or message.begins_with("전리품:"): + return true + return _battle_event_log_matches(message, ["보급고", "치료소", "창고", "군자금"]) + + +func _log_message_is_movement(message: String) -> bool: + for token in ["으로 진군.", "으로 압박.", "으로 접근.", "에 당도.", "에 진형을 잡았다.", "에 닿았다.", "발을 들였다."]: + if message.contains(token): + return true + return message.contains(" moves to ") or message.contains(" advances to ") or message.contains(" arrives at ") + + +func _log_message_is_reinforcement(message: String) -> bool: + return _battle_event_log_matches(message, ["잔병이 합류", "약탈대", "원군", "증원", "마지막 황건대", "마지막 반격", "잔당"]) + + func _announce_battle_status_audio() -> void: if state.battle_status == BattleState.STATUS_ACTIVE: last_announced_battle_status = "" diff --git a/tools/smoke_title_menu.gd b/tools/smoke_title_menu.gd index d6b4541..bd2de18 100644 --- a/tools/smoke_title_menu.gd +++ b/tools/smoke_title_menu.gd @@ -188,8 +188,8 @@ func _check_title_menu_start_load_and_settings(failures: Array[String]) -> void: else: if not scene.title_bgm_toggle.text.contains("군막의 새벽"): failures.append("title BGM toggle should name the active menu theme") - if not scene.title_sfx_toggle.text.contains("클릭") or not scene.title_sfx_toggle.text.contains("발소리") or not scene.title_sfx_toggle.text.contains("전투"): - failures.append("title SFX toggle should clarify menu, movement, and battle effects") + if not scene.title_sfx_toggle.text.contains("클릭") or not scene.title_sfx_toggle.text.contains("발소리") or not scene.title_sfx_toggle.text.contains("전투") or not scene.title_sfx_toggle.text.contains("보급"): + failures.append("title SFX toggle should clarify menu, movement, battle, and supply effects") if not scene.title_fullscreen_toggle.text.contains("전체화면"): failures.append("title fullscreen toggle should be clearly named") if scene.title_bgm_volume_slider == null or scene.title_sfx_volume_slider == null: @@ -237,8 +237,8 @@ func _check_title_menu_start_load_and_settings(failures: Array[String]) -> void: failures.append("title settings should expose a clear audio defaults preview button") else: _check_title_menu_button_art(failures, scene.title_audio_reset_button, "status", "title audio reset") - if not scene.title_audio_reset_button.tooltip_text.contains("확인음") or not scene.title_audio_reset_button.tooltip_text.contains("발소리") or not scene.title_audio_reset_button.tooltip_text.contains("타격음"): - failures.append("title audio preview should explain the confirmation, movement, and strike samples") + if not scene.title_audio_reset_button.tooltip_text.contains("확인음") or not scene.title_audio_reset_button.tooltip_text.contains("발소리") or not scene.title_audio_reset_button.tooltip_text.contains("타격음") or not scene.title_audio_reset_button.tooltip_text.contains("퇴각음") or not scene.title_audio_reset_button.tooltip_text.contains("보급음"): + failures.append("title audio preview should explain the confirmation, movement, strike, defeat, and supply samples") scene.recent_sfx_keys.clear() scene._on_title_audio_reset_pressed() if not scene.title_bgm_enabled or not scene.title_sfx_enabled: @@ -255,6 +255,10 @@ func _check_title_menu_start_load_and_settings(failures: Array[String]) -> void: failures.append("title audio preview should play a movement footstep SFX") if not scene.recent_sfx_keys.has("slash"): failures.append("title audio preview should play a strike SFX") + if not scene.recent_sfx_keys.has("defeat_sting"): + failures.append("title audio preview should play a defeat SFX") + if not scene.recent_sfx_keys.has("item_pickup"): + failures.append("title audio preview should play a supply SFX") scene._on_title_settings_back_pressed() if scene.title_settings_panel != null and scene.title_settings_panel.visible: failures.append("settings back should hide the title settings panel") @@ -470,8 +474,8 @@ func _check_title_menu_start_load_and_settings(failures: Array[String]) -> void: else: if not scene.settings_bgm_toggle.text.contains("군막") or not scene.settings_bgm_toggle.text.contains("전투"): failures.append("in-game BGM toggle should clarify menu and battle music") - if not scene.settings_sfx_toggle.text.contains("선택") or not scene.settings_sfx_toggle.text.contains("발소리") or not scene.settings_sfx_toggle.text.contains("전투"): - failures.append("in-game SFX toggle should clarify menu, movement, and battle effects") + if not scene.settings_sfx_toggle.text.contains("선택") or not scene.settings_sfx_toggle.text.contains("발소리") or not scene.settings_sfx_toggle.text.contains("전투") or not scene.settings_sfx_toggle.text.contains("보급"): + failures.append("in-game SFX toggle should clarify menu, movement, battle, and supply effects") if not scene.settings_fullscreen_toggle.text.contains("전체화면"): failures.append("in-game fullscreen toggle should be clearly named") if scene.settings_bgm_volume_slider == null or scene.settings_sfx_volume_slider == null: @@ -514,6 +518,10 @@ func _check_title_menu_start_load_and_settings(failures: Array[String]) -> void: failures.append("in-game audio preview should play a movement footstep SFX") if not scene.recent_sfx_keys.has("slash"): failures.append("in-game audio preview should play a strike SFX") + if not scene.recent_sfx_keys.has("defeat_sting"): + failures.append("in-game audio preview should play a defeat SFX") + if not scene.recent_sfx_keys.has("item_pickup"): + failures.append("in-game audio preview should play a supply SFX") scene._hide_settings_panel() scene._on_new_campaign_pressed() if not scene._is_opening_prologue_visible(): diff --git a/tools/smoke_visual_assets.gd b/tools/smoke_visual_assets.gd index e1df37f..29cf2e9 100644 --- a/tools/smoke_visual_assets.gd +++ b/tools/smoke_visual_assets.gd @@ -3965,6 +3965,22 @@ func _check_ancient_ui_theme(failures: Array[String]) -> void: failures.append("reinforcement feedback should use the footstep arrival SFX") if not scene._combat_feedback_sfx_key("표적", "priority").is_empty(): failures.append("priority targeting feedback should not inherit reinforcement arrival SFX") + scene.recent_sfx_keys.clear() + scene._play_log_sfx("조조, 서쪽 초원으로 진군.") + if not scene.recent_sfx_keys.has("footstep"): + failures.append("movement logs should play the footstep SFX") + scene.recent_sfx_keys.clear() + scene._play_log_sfx("황건병 퇴각.") + if not scene.recent_sfx_keys.has("defeat_sting"): + failures.append("defeat logs should play the defeat SFX") + scene.recent_sfx_keys.clear() + scene._play_log_sfx("북쪽 숲 보급고를 거두었다.") + if not scene.recent_sfx_keys.has("item_pickup"): + failures.append("supply logs should play the item pickup SFX") + scene.recent_sfx_keys.clear() + scene._play_log_sfx("동쪽 들판에서 황건 잔병이 합류했다.") + if not scene.recent_sfx_keys.has("footstep"): + failures.append("reinforcement logs should play the footstep SFX") scene._on_log_added("북쪽 숲 보급고를 거두었다.") if scene.objective_notice_label == null or not scene.objective_notice_label.text.contains("보급 확보") or not scene.objective_notice_label.text.contains("보급고"): failures.append("supply event log should become a battlefield notice: %s" % ("" if scene.objective_notice_label == null else scene.objective_notice_label.text))