From aadf8b2f986cc2c503aa0d5c9db95884669cd017 Mon Sep 17 00:00:00 2001 From: Wickedness Date: Sat, 20 Jun 2026 10:24:29 +0900 Subject: [PATCH] Expose enemy AI state in battle UI --- scripts/scenes/battle_scene.gd | 59 +++++++++++++++++++++++++++++++--- tools/smoke_visual_assets.gd | 15 +++++++++ 2 files changed, 69 insertions(+), 5 deletions(-) diff --git a/scripts/scenes/battle_scene.gd b/scripts/scenes/battle_scene.gd index f20cccb..94d0f2c 100644 --- a/scripts/scenes/battle_scene.gd +++ b/scripts/scenes/battle_scene.gd @@ -7994,6 +7994,9 @@ func _hover_info_badge_lines(cell: Vector2i) -> Array[String]: int(unit.get("max_mp", 0)) ]) var status_effects := state.get_status_effect_summary(str(unit.get("id", ""))) + var ai_hint := _unit_ai_behavior_hover_line(unit) + if not ai_hint.is_empty(): + lines.append(ai_hint) if not status_effects.is_empty(): lines.append("상태: %s" % status_effects) else: @@ -9539,14 +9542,57 @@ func _unit_ai_behavior_text(unit: Dictionary) -> String: var behavior := str(unit.get("ai_behavior", "")).strip_edges().to_lower() if behavior == "guard": var guard_radius := int(unit.get("ai_guard_radius", 0)) - return "수비: 거점 경계 반경 %d" % guard_radius + return "수비: 거점 경계 반경 %d 안에서 길목을 지킵니다." % guard_radius if behavior == "sentry": var sentry_radius := int(unit.get("ai_sentry_radius", 0)) - var wake_text := "기상" if bool(unit.get("ai_awake", false)) else "대기" - return "초병: 유인선 %s 반경 %d" % [wake_text, sentry_radius] + var activation_turn := int(unit.get("ai_sentry_activation_turn", 0)) + if bool(unit.get("ai_awake", false)): + return "초병: 경계가 풀려 추격 중입니다. 유인 반경 %d" % sentry_radius + if activation_turn > 0: + return "초병: 유인 반경 %d 또는 제%d군령에 움직입니다." % [sentry_radius, activation_turn] + return "초병: 유인 반경 %d 안에 들어오면 움직입니다." % sentry_radius return "" +func _unit_ai_behavior_hover_line(unit: Dictionary) -> String: + if str(unit.get("team", "")) != BattleState.TEAM_ENEMY: + return "" + var behavior := str(unit.get("ai_behavior", "")).strip_edges().to_lower() + if behavior == "guard": + return "수비 · 거점 반경 %d" % int(unit.get("ai_guard_radius", 0)) + if behavior == "sentry": + var sentry_radius := int(unit.get("ai_sentry_radius", 0)) + if bool(unit.get("ai_awake", false)): + return "초병 경계 · 유인 반경 %d" % sentry_radius + var activation_turn := int(unit.get("ai_sentry_activation_turn", 0)) + if activation_turn > 0: + return "초병 대기 · 유인 %d · 제%d군령" % [sentry_radius, activation_turn] + return "초병 대기 · 유인 반경 %d" % sentry_radius + return "" + + +func _unit_ai_behavior_badge_text(unit: Dictionary) -> String: + if str(unit.get("team", "")) != BattleState.TEAM_ENEMY: + return "" + var behavior := str(unit.get("ai_behavior", "")).strip_edges().to_lower() + if behavior == "guard": + return "수비" + if behavior == "sentry": + return "경계" if bool(unit.get("ai_awake", false)) else "대기" + return "적" + + +func _unit_ai_behavior_badge_icon_kind(unit: Dictionary) -> String: + if str(unit.get("team", "")) != BattleState.TEAM_ENEMY: + return "" + var behavior := str(unit.get("ai_behavior", "")).strip_edges().to_lower() + if behavior == "guard": + return "formation" + if behavior == "sentry" and not bool(unit.get("ai_awake", false)): + return "wait" + return "threat" + + func _unit_current_terrain_text(unit: Dictionary) -> String: var cell: Vector2i = unit.get("pos", Vector2i(-1, -1)) if not state.is_inside(cell): @@ -13171,7 +13217,7 @@ 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" + return _unit_ai_behavior_badge_icon_kind(unit) if not bool(unit.get("controllable", true)): return "formation" if bool(unit.get("acted", false)): @@ -13202,7 +13248,7 @@ func _battle_unit_action_state_text(unit: Dictionary) -> String: if not bool(unit.get("alive", true)): return "퇴각" if str(unit.get("team", "")) == BattleState.TEAM_ENEMY: - return "적" + return _unit_ai_behavior_badge_text(unit) if not bool(unit.get("controllable", true)): return "호위" if bool(unit.get("acted", false)): @@ -13214,6 +13260,9 @@ func _battle_unit_action_state_tooltip(unit: Dictionary) -> String: if not bool(unit.get("alive", true)): return "전장을 이탈했습니다." if str(unit.get("team", "")) == BattleState.TEAM_ENEMY: + var ai_text := _unit_ai_behavior_text(unit) + if not ai_text.is_empty(): + return ai_text return "적군 부대입니다." if not bool(unit.get("controllable", true)): return "호위 대상이라 직접 명령할 수 없습니다." diff --git a/tools/smoke_visual_assets.gd b/tools/smoke_visual_assets.gd index 968b3f9..be394fd 100644 --- a/tools/smoke_visual_assets.gd +++ b/tools/smoke_visual_assets.gd @@ -4490,8 +4490,12 @@ func _check_battle_unit_list_panel(failures: Array[String]) -> void: failures.append("enemy battle unit list should keep raw coordinates out of visible badges: %s" % enemy_visible_text) if enemy_visible_text.contains("군령:") or enemy_visible_text.contains("직접 명령"): failures.append("enemy battle unit list rows should keep dense enemy detail in hover text: %s" % enemy_visible_text) + if not (enemy_badge_text.contains("대기") or enemy_badge_text.contains("수비") or enemy_badge_text.contains("경계")): + failures.append("enemy battle unit list should expose compact AI state badges: %s" % enemy_badge_text) if not enemy_tooltips.contains("군령: 적군 군기") or not enemy_tooltips.contains("클릭하면 위치로 이동"): failures.append("enemy battle unit list tooltips should retain enemy detail: %s" % enemy_tooltips) + if not enemy_tooltips.contains("유인 반경") or not enemy_tooltips.contains("거점 경계 반경"): + failures.append("enemy battle unit list tooltips should explain sentry and guard behavior: %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: @@ -4746,6 +4750,17 @@ func _check_hover_intent_badges(failures: Array[String]) -> void: failures.append("enemy hover info badge should use generated threat icon metadata: %s" % str(enemy_hover_badge)) if str(enemy_hover_badge.get("panel", "")) != "hud_jade": failures.append("enemy hover info badge should use generated jade panel metadata: %s" % str(enemy_hover_badge)) + var central_sentry: Dictionary = scene.state.get_unit("yellow_turban_5") + central_sentry["ai_awake"] = false + scene.hover_cell = central_sentry.get("pos", Vector2i(10, 6)) + var sentry_hover_badge := scene._hover_info_badge() + var sentry_hover_text := str(sentry_hover_badge.get("lines", [])) + if not sentry_hover_text.contains("초병 대기") or not sentry_hover_text.contains("제4군령"): + failures.append("sleeping sentry hover badge should expose lure timing compactly: %s" % str(sentry_hover_badge)) + central_sentry["ai_awake"] = true + var awake_sentry_hover_badge := scene._hover_info_badge() + if not str(awake_sentry_hover_badge.get("lines", [])).contains("초병 경계"): + failures.append("awake sentry hover badge should show the changed AI state: %s" % str(awake_sentry_hover_badge)) scene.hover_cell = Vector2i(4, 2) var event_hover_badge := scene._hover_info_badge() if str(event_hover_badge.get("kind", "")) != "event" or not str(event_hover_badge.get("lines", [])).contains("표식 북숲 보급고"):