diff --git a/README.md b/README.md index 0caa461..3009437 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ Godot 4 tactical RPG prototype inspired by classic turn-based Romance of the Thr - Pre-battle prep commands use compact icon buttons with active selection state and Korean hover labels for chapters, shop, talks, armory, roster, formation, and save records. - Post-move tactic and item pickers use generated command/item icons, compact two-line option rows, and hover details instead of dense single-line text. - Target preview badges and battlefield target markers include generated action icons for move, attack, threat, support, and item outcomes. +- Battlefield hover info badges use generated jade panel texture plus terrain, supply, tactic, objective, and threat icons, keeping raw grid coordinates out of visible play text. - Battle, top-toolbar, and pre-battle command buttons now prefer generated high-resolution bronze/parchment icon art under `art/ui/icons` and generated lacquer/jade/cinnabar button surfaces under `art/ui/buttons`, with procedural icon drawing and flat button styles kept only as fallbacks. - Battle maps blend generated terrain texture tiles for plains, forests, hills, wasteland, roads, water, villages, and castles over the high-resolution battlefield backdrop, with generated transparent feature overlays for road connections, water, villages, castles, and wasteland accents. - Core UI panels now prefer generated high-resolution lacquer, scroll, jade, and command-seal frame textures under `art/ui/panels`, with the calmer ink/wood/jade flat styles kept as fallbacks instead of the earlier yellow, hard-edged frame treatment. diff --git a/scripts/scenes/battle_scene.gd b/scripts/scenes/battle_scene.gd index 7f44260..734e7ec 100644 --- a/scripts/scenes/battle_scene.gd +++ b/scripts/scenes/battle_scene.gd @@ -101,6 +101,7 @@ const UNIT_MOVE_ANIMATION_DURATION := 0.18 const UNIT_IDLE_PHASE_SPEED := 2.4 const TARGET_PREVIEW_BADGE_SIZE := Vector2(122, 24) const TARGET_PREVIEW_BADGE_ICON_SIZE := Vector2(18, 18) +const HOVER_INFO_BADGE_ICON_SIZE := Vector2(19, 19) const HOVER_INFO_BADGE_MAX_WIDTH := 196.0 const HOVER_INFO_BADGE_MIN_WIDTH := 126.0 const LOW_HP_WARNING_RATIO := 0.35 @@ -7194,20 +7195,31 @@ func _draw_hover_info_badge() -> void: var cell: Vector2i = badge.get("cell", hover_cell) var badge_rect := _hover_info_badge_rect(cell, lines) var color := _hover_info_badge_color(str(badge.get("kind", "terrain"))) - var background := Color(0.035, 0.022, 0.014, 0.90) + var background := Color(0.035, 0.022, 0.014, 0.72) var font := _draw_ui_font(true) + var panel_texture := _load_art_texture(PANEL_TEXTURE_PATH_TEMPLATE % "hud_jade") + if panel_texture != null: + draw_texture_rect(panel_texture, badge_rect, false, Color(0.70, 0.72, 0.66, 0.92)) draw_rect(badge_rect, background) draw_rect(badge_rect.grow(-2.0), Color(color.r, color.g, color.b, 0.10)) draw_rect(badge_rect, color, false, 2.0) + var icon_texture := _hover_info_badge_icon_texture(str(badge.get("icon", ""))) + if icon_texture != null: + var icon_background := Rect2(badge_rect.position + Vector2(5.0, 4.0), HOVER_INFO_BADGE_ICON_SIZE + Vector2(4.0, 4.0)) + var icon_rect := Rect2(badge_rect.position + Vector2(7.0, 6.0), HOVER_INFO_BADGE_ICON_SIZE) + draw_rect(icon_background, Color(0.020, 0.018, 0.015, 0.62)) + draw_rect(icon_background, Color(color.r, color.g, color.b, 0.44), false, 1.0) + draw_texture_rect(icon_texture, icon_rect, false, Color(1.0, 1.0, 1.0, 0.94)) for index in range(lines.size()): var line_color := color if index == 0 else UI_PARCHMENT_TEXT + var x_offset := 33.0 if index == 0 and icon_texture != null else 8.0 _draw_ink_text( font, - badge_rect.position + Vector2(8.0, 16.0 + float(index) * 15.0), + badge_rect.position + Vector2(x_offset, 16.0 + float(index) * 15.0), lines[index], HORIZONTAL_ALIGNMENT_LEFT, - badge_rect.size.x - 16.0, + badge_rect.size.x - x_offset - 8.0, 12, line_color ) @@ -7239,6 +7251,7 @@ func _hover_info_badge() -> Dictionary: return { "cell": hover_cell, "kind": _hover_info_badge_kind(hover_cell), + "icon": _hover_info_badge_icon_kind(hover_cell), "lines": lines } @@ -7249,6 +7262,11 @@ func _hover_info_badge_lines(cell: Vector2i) -> Array[String]: var summary := state.get_cell_summary(cell) var lines: Array[String] = [] var unit: Dictionary = summary.get("unit", {}) + var terrain_name := _terrain_display_name(str(summary.get("terrain", ""))) + var objective_label := state.get_objective_cell_label(cell) + var event_label := state.get_event_marker_label(cell) + var objective_used_as_title := false + var event_used_as_title := false if not unit.is_empty(): lines.append("%s · %s %s" % [ str(unit.get("name", "부대")), @@ -7265,13 +7283,16 @@ func _hover_info_badge_lines(cell: Vector2i) -> Array[String]: if not status_effects.is_empty(): lines.append("상태: %s" % status_effects) else: - lines.append("%d,%d · %s" % [ - cell.x + 1, - cell.y + 1, - _terrain_display_name(str(summary.get("terrain", ""))) - ]) + if not objective_label.is_empty(): + lines.append("목표 %s" % objective_label) + objective_used_as_title = true + elif not event_label.is_empty(): + lines.append("표식 %s" % event_label) + event_used_as_title = true + else: + lines.append(terrain_name) var terrain_line := "지형 %s · 방비 +%d · 회피 +%d%%" % [ - _terrain_display_name(str(summary.get("terrain", ""))), + terrain_name, int(summary.get("defense", 0)), int(summary.get("avoid", 0)) ] @@ -7280,11 +7301,9 @@ func _hover_info_badge_lines(cell: Vector2i) -> Array[String]: terrain_line += " · 회복 +%d" % heal lines.append(terrain_line) var markers: Array[String] = [] - var objective_label := state.get_objective_cell_label(cell) - if not objective_label.is_empty(): + if not objective_label.is_empty() and not objective_used_as_title: markers.append("목표 %s" % objective_label) - var event_label := state.get_event_marker_label(cell) - if not event_label.is_empty(): + if not event_label.is_empty() and not event_used_as_title: markers.append("표식 %s" % event_label) if show_threat_overlay: var threat_names := state.get_threatening_unit_names(cell, BattleState.TEAM_ENEMY) @@ -7309,6 +7328,34 @@ func _hover_info_badge_kind(cell: Vector2i) -> String: return "terrain" +func _hover_info_badge_icon_kind(cell: Vector2i) -> String: + var unit := state.get_unit_at(cell) + if not unit.is_empty(): + return "unit_list" if str(unit.get("team", "")) == BattleState.TEAM_PLAYER else "threat" + if not state.get_objective_cell_label(cell).is_empty(): + return "objective" + var event_kind := state.get_event_marker_kind(cell) + if event_kind == "tactic": + return "tactic" + if event_kind == "supply": + return "item" + if event_kind == "gold": + return "shop" + if not state.get_event_marker_label(cell).is_empty(): + return "status" + var summary := state.get_cell_summary(cell) + if int(summary.get("heal", 0)) > 0: + return "item" + return "terrain" + + +func _hover_info_badge_icon_texture(icon_kind: String) -> Texture2D: + var normalized := icon_kind.strip_edges().to_lower() + if normalized.is_empty(): + return null + return _make_toolbar_icon(normalized) + + func _hover_info_badge_rect(cell: Vector2i, lines: Array[String]) -> Rect2: var size := _hover_info_badge_size(lines) var cell_rect := _rect_for_cell(cell) @@ -7329,7 +7376,7 @@ func _hover_info_badge_size(lines: Array[String]) -> Vector2: var max_chars := 8 for line in lines: max_chars = maxi(max_chars, str(line).length()) - var width := clampf(float(max_chars) * 8.2 + 18.0, HOVER_INFO_BADGE_MIN_WIDTH, HOVER_INFO_BADGE_MAX_WIDTH) + var width := clampf(float(max_chars) * 8.2 + 38.0, HOVER_INFO_BADGE_MIN_WIDTH, HOVER_INFO_BADGE_MAX_WIDTH) var height := 13.0 + float(maxi(1, lines.size())) * 15.0 return Vector2(width, height) diff --git a/tools/smoke_visual_assets.gd b/tools/smoke_visual_assets.gd index 36e79d4..e42d962 100644 --- a/tools/smoke_visual_assets.gd +++ b/tools/smoke_visual_assets.gd @@ -4320,19 +4320,33 @@ func _check_hover_intent_badges(failures: Array[String]) -> void: var enemy_hover_badge := scene._hover_info_badge() if str(enemy_hover_badge.get("kind", "")) != "enemy" or not str(enemy_hover_badge.get("lines", [])).contains(str(enemy.get("name", ""))): failures.append("enemy hover info badge should expose enemy identity: %s" % str(enemy_hover_badge)) + if str(enemy_hover_badge.get("icon", "")) != "threat": + failures.append("enemy hover info badge should use generated threat icon metadata: %s" % str(enemy_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("표식 북숲 보급고"): failures.append("event hover info badge should expose marker label: %s" % str(event_hover_badge)) + if str(event_hover_badge.get("icon", "")) != "item": + failures.append("event hover info badge should use generated supply icon metadata: %s" % str(event_hover_badge)) + if str(event_hover_badge.get("lines", [])).contains("5,3"): + failures.append("event hover info badge should not lead with raw grid coordinates: %s" % str(event_hover_badge)) scene.hover_cell = Vector2i(4, 6) var tactic_hover_badge := scene._hover_info_badge() if str(tactic_hover_badge.get("kind", "")) != "event" or not str(tactic_hover_badge.get("lines", [])).contains("표식 유인선"): failures.append("tactic hover info badge should expose lure-line marker label: %s" % str(tactic_hover_badge)) + if str(tactic_hover_badge.get("icon", "")) != "tactic": + failures.append("tactic hover info badge should use generated tactic icon metadata: %s" % str(tactic_hover_badge)) + if str(tactic_hover_badge.get("lines", [])).contains("5,7"): + failures.append("tactic hover info badge should hide raw grid coordinates: %s" % str(tactic_hover_badge)) scene.hover_cell = Vector2i(20, 1) var objective_hover_badge := scene._hover_info_badge() var objective_hover_text := str(objective_hover_badge.get("lines", [])) if str(objective_hover_badge.get("kind", "")) != "objective" or not objective_hover_text.contains("목표 성채 장악") or not objective_hover_text.contains("회복 +8"): failures.append("objective hover info badge should expose capture and recovery: %s" % str(objective_hover_badge)) + if str(objective_hover_badge.get("icon", "")) != "objective": + failures.append("objective hover info badge should use generated objective icon metadata: %s" % str(objective_hover_badge)) + if objective_hover_text.contains("21,2"): + failures.append("objective hover info badge should not show raw coordinates in visible text: %s" % objective_hover_text) var edge_hover_lines: Array[String] = ["긴 전장 배지", "둘째 줄"] var edge_hover_rect: Rect2 = scene._hover_info_badge_rect(Vector2i(13, 9), edge_hover_lines) if edge_hover_rect.position.x < board_rect.position.x or edge_hover_rect.position.y < board_rect.position.y: