From b84cadc904fcd5d2d0e6e0c50b5a1556e322e15b Mon Sep 17 00:00:00 2001 From: Wickedness Date: Sat, 20 Jun 2026 06:50:11 +0900 Subject: [PATCH] Add visual crests to battle unit list --- scripts/scenes/battle_scene.gd | 122 +++++++++++++++++++++++++++++++-- tools/smoke_visual_assets.gd | 23 +++++++ 2 files changed, 138 insertions(+), 7 deletions(-) diff --git a/scripts/scenes/battle_scene.gd b/scripts/scenes/battle_scene.gd index 5099ba8..c2ea73d 100644 --- a/scripts/scenes/battle_scene.gd +++ b/scripts/scenes/battle_scene.gd @@ -183,7 +183,10 @@ const PREP_MENU_BUTTON_SIZE := Vector2(48, 38) const BATTLE_UNIT_LIST_PANEL_SIZE := Vector2(420, 360) const BATTLE_UNIT_LIST_ROW_SIZE := Vector2(372, 58) const BATTLE_UNIT_LIST_ICON_SIZE := Vector2(46, 46) +const BATTLE_UNIT_LIST_CLASS_CREST_PANEL_SIZE := Vector2(22, 22) +const BATTLE_UNIT_LIST_CLASS_CREST_ICON_SIZE := Vector2(17, 17) const BATTLE_UNIT_LIST_BADGE_SIZE := Vector2(72, 22) +const BATTLE_UNIT_LIST_BADGE_ICON_SIZE := Vector2(15, 15) const SETTINGS_PANEL_SIZE := Vector2(420, 330) const LOCAL_COMMAND_BUTTON_SIZE := Vector2(92, 38) const LOCAL_COMMAND_CANCEL_BUTTON_SIZE := Vector2(196, 30) @@ -2590,9 +2593,26 @@ func _create_hud() -> void: battle_unit_header_row.add_theme_constant_override("separation", 8) battle_unit_column.add_child(battle_unit_header_row) + var battle_unit_header_tooltip := "군세\n전장에 있는 아군과 적군의 상태를 봅니다." + var battle_unit_header_seal := _make_seal_tile("군", 26) + battle_unit_header_seal.name = "BattleUnitListHeaderSeal" + battle_unit_header_seal.tooltip_text = battle_unit_header_tooltip + battle_unit_header_row.add_child(battle_unit_header_seal) + + var battle_unit_header_icon := TextureRect.new() + battle_unit_header_icon.name = "BattleUnitListHeaderIcon" + battle_unit_header_icon.custom_minimum_size = Vector2(24, 24) + battle_unit_header_icon.size_flags_vertical = Control.SIZE_SHRINK_CENTER + battle_unit_header_icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE + battle_unit_header_icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED + battle_unit_header_icon.texture = _make_toolbar_icon("unit_list") + battle_unit_header_icon.tooltip_text = battle_unit_header_tooltip + battle_unit_header_row.add_child(battle_unit_header_icon) + battle_unit_list_title_label = Label.new() battle_unit_list_title_label.text = "군세" - battle_unit_list_title_label.custom_minimum_size = Vector2(232, 28) + battle_unit_list_title_label.tooltip_text = battle_unit_header_tooltip + battle_unit_list_title_label.custom_minimum_size = Vector2(174, 28) battle_unit_list_title_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL _apply_label_style(battle_unit_list_title_label, UI_OLD_BRONZE, 16) battle_unit_header_row.add_child(battle_unit_list_title_label) @@ -12403,11 +12423,17 @@ func _make_battle_unit_list_row(unit: Dictionary) -> HBoxContainer: badge_row.tooltip_text = tooltip_text column.add_child(badge_row) - for badge_text in _format_battle_unit_list_row_badges(unit): + var badge_texts := _format_battle_unit_list_row_badges(unit) + for badge_index in range(badge_texts.size()): + var badge_text = badge_texts[badge_index] var text := str(badge_text).strip_edges() if text.is_empty(): continue - badge_row.add_child(_make_battle_unit_list_badge(text, tooltip_text)) + badge_row.add_child(_make_battle_unit_list_badge( + text, + tooltip_text, + _battle_unit_list_badge_icon(unit, badge_index, text) + )) return row @@ -12443,6 +12469,30 @@ func _make_battle_unit_list_icon(unit: Dictionary, tooltip_text: String) -> Pane initials_label.tooltip_text = tooltip_text _apply_label_style(initials_label, UI_PARCHMENT_TEXT, 14) stack.add_child(initials_label) + + var class_icon := _load_unit_class_icon_texture(unit) + if class_icon != null: + var crest_panel := PanelContainer.new() + crest_panel.name = "BattleUnitListClassCrest" + crest_panel.position = Vector2( + BATTLE_UNIT_LIST_ICON_SIZE.x - BATTLE_UNIT_LIST_CLASS_CREST_PANEL_SIZE.x + 1.0, + BATTLE_UNIT_LIST_ICON_SIZE.y - BATTLE_UNIT_LIST_CLASS_CREST_PANEL_SIZE.y + 1.0 + ) + crest_panel.size = BATTLE_UNIT_LIST_CLASS_CREST_PANEL_SIZE + crest_panel.custom_minimum_size = BATTLE_UNIT_LIST_CLASS_CREST_PANEL_SIZE + crest_panel.tooltip_text = "%s\n%s" % [_unit_class_display_name(unit), tooltip_text] + _apply_panel_style(crest_panel, "caption") + + var crest_icon := TextureRect.new() + crest_icon.name = "BattleUnitListClassIcon" + crest_icon.custom_minimum_size = BATTLE_UNIT_LIST_CLASS_CREST_ICON_SIZE + crest_icon.size = BATTLE_UNIT_LIST_CLASS_CREST_ICON_SIZE + crest_icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE + crest_icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED + crest_icon.texture = class_icon + crest_icon.tooltip_text = crest_panel.tooltip_text + crest_panel.add_child(crest_icon) + stack.add_child(crest_panel) return panel @@ -12462,23 +12512,81 @@ func _format_battle_unit_list_row_badges(unit: Dictionary) -> Array: ] -func _make_battle_unit_list_badge(text: String, tooltip_text: String) -> PanelContainer: +func _make_battle_unit_list_badge(text: String, tooltip_text: String, icon_texture: Texture2D = null) -> PanelContainer: var panel := PanelContainer.new() panel.custom_minimum_size = BATTLE_UNIT_LIST_BADGE_SIZE panel.tooltip_text = tooltip_text _apply_panel_style(panel, "caption") + if icon_texture != null: + panel.set_meta("battle_unit_list_badge_icon", true) + + var row := HBoxContainer.new() + row.custom_minimum_size = Vector2(BATTLE_UNIT_LIST_BADGE_SIZE.x - 8.0, BATTLE_UNIT_LIST_BADGE_SIZE.y - 4.0) + row.add_theme_constant_override("separation", 3) + row.tooltip_text = tooltip_text + panel.add_child(row) + + if icon_texture != null: + var icon := TextureRect.new() + icon.name = "BattleUnitListBadgeIcon" + icon.custom_minimum_size = BATTLE_UNIT_LIST_BADGE_ICON_SIZE + icon.size_flags_vertical = Control.SIZE_SHRINK_CENTER + icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE + icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED + icon.texture = icon_texture + icon.tooltip_text = tooltip_text + row.add_child(icon) var label := Label.new() - label.set_anchors_preset(Control.PRESET_FULL_RECT) - label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + label.custom_minimum_size = Vector2(44, BATTLE_UNIT_LIST_BADGE_SIZE.y - 4.0) + label.size_flags_horizontal = Control.SIZE_EXPAND_FILL + label.horizontal_alignment = HORIZONTAL_ALIGNMENT_LEFT if icon_texture != null else HORIZONTAL_ALIGNMENT_CENTER label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER label.text = text label.tooltip_text = tooltip_text _apply_label_style(label, UI_PARCHMENT_TEXT, 10) - panel.add_child(label) + row.add_child(label) return panel +func _battle_unit_list_badge_icon(unit: Dictionary, badge_index: int, badge_text: String) -> Texture2D: + match badge_index: + 0: + return _load_unit_class_icon_texture(unit) + 1: + return _make_toolbar_icon("status") + 2: + return _make_toolbar_icon(_battle_unit_action_icon_kind(unit)) + 3: + return _make_toolbar_icon(_battle_zone_icon_kind(badge_text)) + _: + return null + + +func _battle_unit_action_icon_kind(unit: Dictionary) -> String: + if not bool(unit.get("alive", true)): + return "cancel" + if str(unit.get("team", "")) == BattleState.TEAM_ENEMY: + return "threat" + if not bool(unit.get("controllable", true)): + return "formation" + if bool(unit.get("acted", false)): + return "wait" + return "move" + + +func _battle_zone_icon_kind(zone_text: String) -> String: + match zone_text: + "성채": + return "objective" + "마을": + return "shop" + "위치 미상": + return "status" + _: + return "terrain" + + func _format_battle_unit_list_row_tooltip(unit: Dictionary) -> String: var hint := "클릭하면 위치로 이동합니다." if str(unit.get("team", "")) == BattleState.TEAM_PLAYER and _can_select(unit) and _can_select_from_battle_unit_list(): diff --git a/tools/smoke_visual_assets.gd b/tools/smoke_visual_assets.gd index 9bb95e7..28b86ab 100644 --- a/tools/smoke_visual_assets.gd +++ b/tools/smoke_visual_assets.gd @@ -3947,6 +3947,15 @@ func _find_descendant_by_name(root: Node, node_name: String) -> Node: return null +func _count_descendants_by_name(root: Node, node_name: String) -> int: + if root == null: + return 0 + var count := 1 if str(root.name).begins_with(node_name) else 0 + for child in root.get_children(): + count += _count_descendants_by_name(child, node_name) + return count + + func _is_descendant_of(node: Node, ancestor: Node) -> bool: var current := node while current != null: @@ -4171,6 +4180,12 @@ func _check_battle_unit_list_panel(failures: Array[String]) -> void: else: _check_panel_style_fill(failures, scene.battle_unit_list_panel, "battle unit list panel", Color(0.045, 0.048, 0.044, 0.93)) _check_panel_uses_generated_texture(failures, scene.battle_unit_list_panel, "battle unit list generated frame") + var header_icon := _find_descendant_by_name(scene.battle_unit_list_panel, "BattleUnitListHeaderIcon") + if header_icon == null or not header_icon is TextureRect or (header_icon as TextureRect).texture == null: + failures.append("battle unit list header should carry generated unit-list icon artwork") + var header_seal := _find_descendant_by_name(scene.battle_unit_list_panel, "BattleUnitListHeaderSeal") + if header_seal == null: + failures.append("battle unit list header should use a compact Korean seal mark") if scene.battle_unit_list_title_label == null or not scene.battle_unit_list_title_label.text.contains("아군"): failures.append("battle unit list should open on the ally tab") if scene.battle_unit_list_rows == null or scene.battle_unit_list_rows.get_child_count() < 2: @@ -4189,6 +4204,10 @@ func _check_battle_unit_list_panel(failures: Array[String]) -> void: failures.append("ally battle unit list tooltips should retain selection detail: %s" % ally_tooltips) if not _has_descendant_texture(scene.battle_unit_list_rows): failures.append("ally battle unit list should show officer portrait artwork") + if _find_descendant_by_name(scene.battle_unit_list_rows, "BattleUnitListClassIcon") == null: + failures.append("ally battle unit list rows should overlay generated class crest artwork") + if _count_descendants_by_name(scene.battle_unit_list_rows, "BattleUnitListBadgeIcon") < 4: + failures.append("ally battle unit list compact badges should include icon artwork for class, HP, action, and zone") scene._on_battle_unit_list_row_pressed("cao_cao") var selected: Dictionary = scene.state.get_selected_unit() @@ -4218,6 +4237,10 @@ func _check_battle_unit_list_panel(failures: Array[String]) -> void: failures.append("enemy battle unit list tooltips should retain enemy detail: %s" % enemy_tooltips) if not _has_descendant_texture(scene.battle_unit_list_rows): failures.append("enemy battle unit list should show unit sprite artwork") + if _find_descendant_by_name(scene.battle_unit_list_rows, "BattleUnitListClassIcon") == null: + failures.append("enemy battle unit list rows should overlay generated class crest artwork") + if _count_descendants_by_name(scene.battle_unit_list_rows, "BattleUnitListBadgeIcon") < 4: + failures.append("enemy battle unit list compact badges should include icon artwork for class, HP, action, and zone") scene._on_battle_unit_list_row_pressed("yellow_turban_1") selected = scene.state.get_selected_unit() if selected.is_empty() or str(selected.get("id", "")) != "cao_cao":