diff --git a/scripts/scenes/battle_scene.gd b/scripts/scenes/battle_scene.gd index 23646b4..f3a9c0d 100644 --- a/scripts/scenes/battle_scene.gd +++ b/scripts/scenes/battle_scene.gd @@ -248,6 +248,8 @@ const POST_MOVE_PICKER_TITLE_SIZE := Vector2(296, 22) const POST_MOVE_PICKER_DETAIL_SIZE := Vector2(296, 32) const POST_MOVE_PICKER_OPTION_SIZE := Vector2(296, 46) const POST_MOVE_PICKER_OPTION_ICON_MAX_WIDTH := 34 +const SIDE_MENU_OPTION_SIZE := Vector2(420, 46) +const SIDE_MENU_OPTION_ICON_MAX_WIDTH := 34 const TARGETING_HINT_PANEL_SIZE := Vector2(254, 118) const TARGETING_HINT_TITLE_SIZE := Vector2(220, 22) const TARGETING_HINT_DETAIL_SIZE := Vector2(220, 28) @@ -3604,10 +3606,12 @@ func _create_hud() -> void: objective_notice_panel = PanelContainer.new() objective_notice_panel.visible = false - objective_notice_panel.position = Vector2(338, 88) + objective_notice_panel.position = Vector2(126, 92) objective_notice_panel.size = Vector2(604, 232) objective_notice_panel.custom_minimum_size = Vector2(604, 232) objective_notice_panel.mouse_filter = Control.MOUSE_FILTER_PASS + objective_notice_panel.z_as_relative = false + objective_notice_panel.z_index = 72 _apply_panel_style(objective_notice_panel, "notice_edict") root.add_child(objective_notice_panel) @@ -18560,7 +18564,12 @@ func _rebuild_tactic_menu(selected: Dictionary) -> void: var skill := state.get_skill_def(str(skill_id)) var skill_button := Button.new() skill_button.text = _format_tactic_button_text(str(skill_id), skill, selected) + skill_button.tooltip_text = _format_tactic_button_tooltip_text(str(skill_id), skill, selected) skill_button.disabled = skill_sealed or selected.get("acted", false) or _is_input_locked() or int(selected.get("mp", 0)) < int(skill.get("mp_cost", 0)) + skill_button.custom_minimum_size = SIDE_MENU_OPTION_SIZE + skill_button.icon = _post_move_tactic_icon_texture(skill) + skill_button.expand_icon = false + skill_button.add_theme_constant_override("icon_max_width", SIDE_MENU_OPTION_ICON_MAX_WIDTH) _apply_button_style(skill_button) skill_button.pressed.connect(_on_tactic_skill_pressed.bind(str(skill_id))) tactic_list.add_child(skill_button) @@ -18580,17 +18589,34 @@ func _clear_tactic_list() -> void: func _format_tactic_button_text(skill_id: String, skill: Dictionary, selected: Dictionary) -> String: var marker := "> " if selected_skill_id == skill_id else "" + var skill_name := str(skill.get("name", skill_id)) + var mp_cost := int(skill.get("mp_cost", 0)) + var range_text := _format_tactic_range_text(skill) + var chips: Array[String] = ["기력 %d" % mp_cost, "거리 %s" % range_text] + if state.is_unit_skill_sealed(selected["id"]): + chips.append("봉인") + elif int(selected.get("mp", 0)) < mp_cost: + chips.append("기력 부족") + return "%s%s\n%s" % [marker, skill_name, _join_strings(chips, " · ")] + + +func _format_tactic_button_tooltip_text(skill_id: String, skill: Dictionary, selected: Dictionary) -> String: var skill_name := str(skill.get("name", skill_id)) var mp_cost := int(skill.get("mp_cost", 0)) var kind := _skill_kind_text(str(skill.get("kind", "skill"))) var range_text := _format_tactic_range_text(skill) var effect_text := _format_tactic_effect_text(skill) - var disabled_text := "" + var lines: Array[String] = [ + skill_name, + "기력 %d · %s · 사거리 %s" % [mp_cost, kind, range_text] + ] + if not effect_text.is_empty(): + lines.append(effect_text) if state.is_unit_skill_sealed(selected["id"]): - disabled_text = " - 봉인" + lines.append("책략이 봉인되었습니다.") elif int(selected.get("mp", 0)) < mp_cost: - disabled_text = " - 기력" - return "%s%s 기력 %d %s 사거리 %s %s%s" % [marker, skill_name, mp_cost, kind, range_text, effect_text, disabled_text] + lines.append("기력이 부족합니다.") + return _join_strings(lines, "\n") func _format_tactic_effect_text(skill: Dictionary) -> String: @@ -18790,6 +18816,8 @@ func _rebuild_item_menu(selected: Dictionary) -> void: var count := int(inventory.get(normalized_id, 0)) var use_button := Button.new() use_button.text = _format_item_button_text(normalized_id, item, count) + use_button.tooltip_text = _format_item_button_tooltip_text(normalized_id, item, count) + use_button.custom_minimum_size = SIDE_MENU_OPTION_SIZE _apply_item_button_icon(use_button, item) use_button.disabled = selected.get("acted", false) or _is_input_locked() or count <= 0 _apply_button_style(use_button) @@ -18812,7 +18840,19 @@ func _clear_item_list() -> void: func _format_item_button_text(item_id: String, item: Dictionary, count: int) -> String: var marker := "> " if selected_item_id == item_id else "" var item_name := _item_display_name(item_id) - return "%s%s 잔량 %d %s" % [marker, item_name, count, _format_item_effect_text(item)] + return "%s%s\n잔량 %d · %s" % [marker, item_name, count, _format_item_effect_text(item)] + + +func _format_item_button_tooltip_text(item_id: String, item: Dictionary, count: int) -> String: + var item_name := _item_display_name(item_id) + var effect_text := _format_item_effect_text(item) + var lines: Array[String] = [ + item_name, + "잔량 %d · %s" % [count, effect_text] + ] + if count <= 0: + lines.append("군수고에 남은 수량이 없습니다.") + return _join_strings(lines, "\n") func _format_item_effect_text(item: Dictionary) -> String: diff --git a/tools/smoke_chapter_one_polish.gd b/tools/smoke_chapter_one_polish.gd index 67eb382..7851736 100644 --- a/tools/smoke_chapter_one_polish.gd +++ b/tools/smoke_chapter_one_polish.gd @@ -584,6 +584,12 @@ func _check_battle_start_notice_sequence(failures: Array[String]) -> void: failures.append("battle start notice should carry compact tactical marker hints: %s" % start_notice_text) elif start_notice_text.contains("(20") or start_notice_text.contains("(21") or start_notice_text.contains(",1"): failures.append("battle start notice should not expose raw grid coordinates: %s" % start_notice_text) + if scene.objective_notice_panel != null: + var notice_right_edge: float = scene.objective_notice_panel.position.x + scene.objective_notice_panel.size.x + if notice_right_edge > BattleSceneScript.SIDE_PANEL_POSITION.x - 12.0: + failures.append("battle start notice should not sit behind the side information panel: %s" % str(scene.objective_notice_panel.get_rect())) + if scene.objective_notice_panel.z_index < 60: + failures.append("battle start notice should stay above ordinary HUD layers") if scene.objective_notice_icon_strip == null or scene.objective_notice_icon_strip.get_child_count() < 3: failures.append("battle start notice should use generated objective, warning, and marker icons") else: diff --git a/tools/smoke_visual_assets.gd b/tools/smoke_visual_assets.gd index f591fe8..62703d2 100644 --- a/tools/smoke_visual_assets.gd +++ b/tools/smoke_visual_assets.gd @@ -3549,6 +3549,7 @@ func _check_hud_focus_text(failures: Array[String]) -> void: failures.append("inventory HUD summary should stay compact and leave details to tooltip: %s" % ("" if scene.inventory_label == null else scene.inventory_label.text)) elif not scene.inventory_label.tooltip_text.contains("군수:"): failures.append("inventory HUD tooltip should preserve full inventory detail: %s" % scene.inventory_label.tooltip_text) + _check_side_action_option_text(failures, scene, cao_cao) if scene.inventory_status_panel == null: failures.append("inventory HUD summary should sit inside a readable generated status chip") else: @@ -3691,6 +3692,70 @@ func _check_hud_focus_text(failures: Array[String]) -> void: scene.free() +func _check_side_action_option_text(failures: Array[String], scene, selected: Dictionary) -> void: + var spark: Dictionary = scene.state.get_skill_def("spark") + if spark.is_empty(): + failures.append("side action option text check needs Spark skill data") + else: + var tactic_text: String = scene._format_tactic_button_text("spark", spark, selected) + var tactic_tooltip: String = scene._format_tactic_button_tooltip_text("spark", spark, selected) + if not tactic_text.contains("\n") or tactic_text.contains("위력") or tactic_text.contains("사거리"): + failures.append("side tactic option visible text should stay compact and two-line: %s" % tactic_text) + if not tactic_tooltip.contains("위력") or not tactic_tooltip.contains("사거리"): + failures.append("side tactic option tooltip should preserve full detail: %s" % tactic_tooltip) + + var bean: Dictionary = scene.state.get_item_def("bean") + if bean.is_empty(): + failures.append("side action option text check needs Bean item data") + else: + var item_text: String = scene._format_item_button_text("bean", bean, 2) + var item_tooltip: String = scene._format_item_button_tooltip_text("bean", bean, 2) + if not item_text.contains("\n") or item_text.contains("군수:") or item_text.contains("소모품"): + failures.append("side item option visible text should stay compact and two-line: %s" % item_text) + if not item_tooltip.contains("잔량 2") or not item_tooltip.contains("병력"): + failures.append("side item option tooltip should preserve item detail: %s" % item_tooltip) + + scene._show_tactic_menu(selected) + var tactic_button := _first_button_child(scene.tactic_list) + if tactic_button == null: + failures.append("side tactic menu should create option buttons") + else: + if not tactic_button.text.contains("\n") or tactic_button.text.contains("위력") or tactic_button.text.contains("사거리"): + failures.append("side tactic option button should show compact text: %s" % tactic_button.text) + if tactic_button.tooltip_text.strip_edges().is_empty() or not tactic_button.tooltip_text.contains("사거리"): + failures.append("side tactic option button should keep detailed tooltip: %s" % tactic_button.tooltip_text) + if tactic_button.icon == null or tactic_button.get_theme_constant("icon_max_width") < BattleSceneScript.SIDE_MENU_OPTION_ICON_MAX_WIDTH: + failures.append("side tactic option button should use a generated icon and stable icon width") + if tactic_button.custom_minimum_size != BattleSceneScript.SIDE_MENU_OPTION_SIZE: + failures.append("side tactic option button should reserve stable row size: %s" % str(tactic_button.custom_minimum_size)) + scene._hide_tactic_menu() + + scene.state.set_inventory_snapshot({"bean": 2}) + scene._show_item_menu(selected) + var item_button := _first_button_child(scene.item_list) + if item_button == null: + failures.append("side item menu should create option buttons") + else: + if not item_button.text.contains("\n") or item_button.text.contains("군수:") or item_button.text.contains("소모품"): + failures.append("side item option button should show compact text: %s" % item_button.text) + if item_button.tooltip_text.strip_edges().is_empty() or not item_button.tooltip_text.contains("잔량 2"): + failures.append("side item option button should keep detailed tooltip: %s" % item_button.tooltip_text) + if item_button.icon == null: + failures.append("side item option button should use generated item icon art") + if item_button.custom_minimum_size != BattleSceneScript.SIDE_MENU_OPTION_SIZE: + failures.append("side item option button should reserve stable row size: %s" % str(item_button.custom_minimum_size)) + scene._hide_item_menu() + + +func _first_button_child(root: Node) -> Button: + if root == null: + return null + for child in root.get_children(): + if child is Button: + return child as Button + return null + + func _check_unit_class_family(failures: Array[String], scene, unit_id: String, expected_family: String) -> void: var unit: Dictionary = scene.state.get_unit(unit_id) if unit.is_empty():