Add icons to target preview markers
This commit is contained in:
@@ -36,6 +36,7 @@ Godot 4 tactical RPG prototype inspired by classic turn-based Romance of the Thr
|
||||
- Pre-battle briefing shows the campaign chapter, chapter battle number, location, victory/defeat summary, first-clear reward preview, generated battlefield thumbnail, allied portrait/enemy unit force markers, and image-first tactical marker cards for lure lines and supply points.
|
||||
- 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.
|
||||
- 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.
|
||||
|
||||
@@ -99,7 +99,8 @@ const BATTLE_PRESENTATION_STEP_GAP := 0.08
|
||||
const BATTLE_PRESENTATION_FEEDBACK_LAG := 0.10
|
||||
const UNIT_MOVE_ANIMATION_DURATION := 0.18
|
||||
const UNIT_IDLE_PHASE_SPEED := 2.4
|
||||
const TARGET_PREVIEW_BADGE_SIZE := Vector2(104, 22)
|
||||
const TARGET_PREVIEW_BADGE_SIZE := Vector2(122, 24)
|
||||
const TARGET_PREVIEW_BADGE_ICON_SIZE := Vector2(18, 18)
|
||||
const HOVER_INFO_BADGE_MAX_WIDTH := 196.0
|
||||
const HOVER_INFO_BADGE_MIN_WIDTH := 126.0
|
||||
const LOW_HP_WARNING_RATIO := 0.35
|
||||
@@ -5865,7 +5866,7 @@ func _draw_target_selection_markers() -> void:
|
||||
_draw_tile_marker("target", rect, Color(1.0, 1.0, 1.0, 0.72))
|
||||
draw_rect(marker_rect, Color(0.025, 0.028, 0.034, 0.88))
|
||||
draw_rect(marker_rect, color, false, 1.4)
|
||||
_draw_ink_text(font, marker_rect.position + Vector2(0, 13), str(marker.get("text", "표")), HORIZONTAL_ALIGNMENT_CENTER, marker_rect.size.x, 12, color)
|
||||
_draw_target_marker_label(font, marker_rect, str(marker.get("text", "표")), color, str(marker.get("icon", "")))
|
||||
|
||||
|
||||
func _draw_auto_attack_origin_marker(marker: Dictionary, target_color: Color, font: Font) -> void:
|
||||
@@ -5896,7 +5897,28 @@ func _draw_auto_attack_origin_marker(marker: Dictionary, target_color: Color, fo
|
||||
_draw_tile_marker("move", origin_rect, Color(1.0, 1.0, 1.0, 0.56))
|
||||
draw_rect(marker_rect, Color(0.025, 0.028, 0.034, 0.86))
|
||||
draw_rect(marker_rect, move_color, false, 1.2)
|
||||
_draw_ink_text(font, marker_rect.position + Vector2(0, 13), "진", HORIZONTAL_ALIGNMENT_CENTER, marker_rect.size.x, 12, move_color)
|
||||
_draw_target_marker_label(font, marker_rect, "진", move_color, "move")
|
||||
|
||||
|
||||
func _draw_target_marker_label(font: Font, marker_rect: Rect2, text: String, color: Color, icon_kind: String = "") -> void:
|
||||
var resolved_icon := icon_kind
|
||||
if resolved_icon.is_empty():
|
||||
resolved_icon = _target_preview_badge_icon_kind("damage")
|
||||
var icon_texture := _make_toolbar_icon(resolved_icon)
|
||||
var icon_size := Vector2(13.0, 13.0)
|
||||
var icon_space := icon_size.x + 4.0 if icon_texture != null else 0.0
|
||||
if icon_texture != null:
|
||||
var icon_rect := Rect2(marker_rect.position + Vector2(3.0, (marker_rect.size.y - icon_size.y) * 0.5), icon_size)
|
||||
draw_texture_rect(icon_texture, icon_rect, false, Color(1.0, 1.0, 1.0, 0.92))
|
||||
_draw_ink_text(
|
||||
font,
|
||||
marker_rect.position + Vector2(icon_space, 13.0),
|
||||
text,
|
||||
HORIZONTAL_ALIGNMENT_LEFT if icon_texture != null else HORIZONTAL_ALIGNMENT_CENTER,
|
||||
marker_rect.size.x - icon_space - 2.0,
|
||||
12,
|
||||
color
|
||||
)
|
||||
|
||||
|
||||
func _auto_attack_origin_marker_rect(origin: Vector2i) -> Rect2:
|
||||
@@ -5958,11 +5980,13 @@ func _attack_target_marker_entries() -> Array[Dictionary]:
|
||||
var result_text := "퇴각" if bool(preview.get("would_defeat", false)) else "-%d" % int(preview.get("damage", 0))
|
||||
var directional_text := _format_directional_badge_suffix(preview)
|
||||
var marker_text := "%s%s" % [result_text, directional_text] if not directional_text.is_empty() else "%s %d%%" % [result_text, hit_chance]
|
||||
var marker_kind := _attack_preview_badge_kind(preview)
|
||||
result.append({
|
||||
"cell": cell,
|
||||
"target_id": str(target.get("id", "")),
|
||||
"text": marker_text,
|
||||
"kind": _attack_preview_badge_kind(preview)
|
||||
"kind": marker_kind,
|
||||
"icon": _target_preview_badge_icon_kind(marker_kind)
|
||||
})
|
||||
if not basic_attack_targeting and not _has_pending_move():
|
||||
_append_auto_attack_target_marker_entries(result, selected)
|
||||
@@ -5990,11 +6014,13 @@ func _append_auto_attack_target_marker_entries(result: Array[Dictionary], select
|
||||
var preview := _auto_attack_preview_for_origin(selected, target, attack_origin)
|
||||
if preview.is_empty():
|
||||
continue
|
||||
var marker_kind := _auto_attack_preview_badge_kind(preview)
|
||||
result.append({
|
||||
"cell": target_cell,
|
||||
"target_id": target_id,
|
||||
"text": "행공",
|
||||
"kind": _auto_attack_preview_badge_kind(preview),
|
||||
"kind": marker_kind,
|
||||
"icon": _target_preview_badge_icon_kind(marker_kind),
|
||||
"origin": attack_origin,
|
||||
"requires_move": true,
|
||||
"damage": int(preview.get("damage", 0)),
|
||||
@@ -6023,11 +6049,13 @@ func _skill_target_marker_entries() -> Array[Dictionary]:
|
||||
var badge := _skill_target_preview_badge_for_cell(selected, target, cell)
|
||||
if badge.is_empty() or str(badge.get("kind", "")) == "invalid":
|
||||
continue
|
||||
var marker_kind := str(badge.get("kind", "support"))
|
||||
result.append({
|
||||
"cell": cell,
|
||||
"target_id": str(target.get("id", "")) if not target.is_empty() else "",
|
||||
"text": str(badge.get("text", "표")),
|
||||
"kind": str(badge.get("kind", "support"))
|
||||
"kind": marker_kind,
|
||||
"icon": str(badge.get("icon", _target_preview_badge_icon_kind(marker_kind)))
|
||||
})
|
||||
return result
|
||||
|
||||
@@ -6044,11 +6072,13 @@ func _item_target_marker_entries() -> Array[Dictionary]:
|
||||
var badge := _item_target_preview_badge_for_cell(selected, target, cell)
|
||||
if badge.is_empty() or str(badge.get("kind", "")) == "invalid":
|
||||
continue
|
||||
var marker_kind := str(badge.get("kind", "support"))
|
||||
result.append({
|
||||
"cell": cell,
|
||||
"target_id": str(target.get("id", "")) if not target.is_empty() else "",
|
||||
"text": str(badge.get("text", "표")),
|
||||
"kind": str(badge.get("kind", "support"))
|
||||
"kind": marker_kind,
|
||||
"icon": str(badge.get("icon", _target_preview_badge_icon_kind(marker_kind)))
|
||||
})
|
||||
return result
|
||||
|
||||
@@ -7132,10 +7162,26 @@ func _draw_target_preview_badge() -> void:
|
||||
var background := Color(0.025, 0.028, 0.034, 0.88)
|
||||
var font := _draw_ui_font(true)
|
||||
var text := str(badge.get("text", ""))
|
||||
var icon_texture := _target_preview_badge_icon_texture(kind)
|
||||
var icon_space := TARGET_PREVIEW_BADGE_ICON_SIZE.x + 8.0 if icon_texture != null else 0.0
|
||||
|
||||
draw_rect(badge_rect, background)
|
||||
draw_rect(badge_rect, color, false, 2.0)
|
||||
_draw_ink_text(font, badge_rect.position + Vector2(0, 16), text, HORIZONTAL_ALIGNMENT_CENTER, badge_rect.size.x, 14, color)
|
||||
if icon_texture != null:
|
||||
var icon_rect := Rect2(
|
||||
badge_rect.position + Vector2(5.0, (badge_rect.size.y - TARGET_PREVIEW_BADGE_ICON_SIZE.y) * 0.5),
|
||||
TARGET_PREVIEW_BADGE_ICON_SIZE
|
||||
)
|
||||
draw_texture_rect(icon_texture, icon_rect, false, Color(1.0, 1.0, 1.0, 0.96))
|
||||
_draw_ink_text(
|
||||
font,
|
||||
badge_rect.position + Vector2(icon_space, 17.0),
|
||||
text,
|
||||
HORIZONTAL_ALIGNMENT_LEFT if icon_texture != null else HORIZONTAL_ALIGNMENT_CENTER,
|
||||
badge_rect.size.x - icon_space - 4.0,
|
||||
14,
|
||||
color
|
||||
)
|
||||
|
||||
|
||||
func _draw_hover_info_badge() -> void:
|
||||
@@ -7590,10 +7636,39 @@ func _make_target_preview_badge_at(text: String, kind: String, cell: Vector2i) -
|
||||
return {
|
||||
"text": text,
|
||||
"kind": kind,
|
||||
"cell": cell
|
||||
"cell": cell,
|
||||
"icon": _target_preview_badge_icon_kind(kind)
|
||||
}
|
||||
|
||||
|
||||
func _target_preview_badge_icon_kind(kind: String) -> String:
|
||||
var normalized := kind.strip_edges().to_lower()
|
||||
if normalized == "move":
|
||||
return "move"
|
||||
if normalized == "select":
|
||||
return "status"
|
||||
if normalized == "heal":
|
||||
return "item"
|
||||
if normalized == "mp":
|
||||
return "tactic"
|
||||
if normalized == "support" or normalized == "debuff" or normalized == "poison" or normalized == "seal" or normalized == "snare":
|
||||
return "status"
|
||||
if normalized == "counter" or normalized == "danger":
|
||||
return "threat"
|
||||
if normalized == "invalid":
|
||||
return "cancel"
|
||||
if normalized == "advance":
|
||||
return "move"
|
||||
return "attack"
|
||||
|
||||
|
||||
func _target_preview_badge_icon_texture(kind: String) -> Texture2D:
|
||||
var icon_kind := _target_preview_badge_icon_kind(kind)
|
||||
if icon_kind.is_empty():
|
||||
return null
|
||||
return _make_toolbar_icon(icon_kind)
|
||||
|
||||
|
||||
func _compact_support_preview_text(effect_text: String) -> String:
|
||||
var parts := []
|
||||
for raw_part in effect_text.split(","):
|
||||
|
||||
@@ -500,6 +500,8 @@ func _check_scene_post_move_attack_targeting(failures: Array[String]) -> void:
|
||||
var markers := scene._attack_target_marker_entries()
|
||||
if markers.size() != 1 or markers[0].get("cell", Vector2i.ZERO) != Vector2i(3, 3):
|
||||
failures.append("attack target markers should include only the reachable enemy: %s" % str(markers))
|
||||
elif str(markers[0].get("icon", "")).strip_edges().is_empty():
|
||||
failures.append("attack target markers should carry generated icon hints: %s" % str(markers))
|
||||
|
||||
scene._on_targeting_back_pressed()
|
||||
cao_cao = scene.state.get_unit("cao_cao")
|
||||
@@ -579,6 +581,8 @@ func _check_scene_enemy_click_auto_attack(failures: Array[String]) -> void:
|
||||
failures.append("move+attack marker origin should differ from the current cell: %s" % str(marker))
|
||||
if not bool(marker.get("requires_move", false)):
|
||||
failures.append("move+attack marker should flag that movement is required: %s" % str(marker))
|
||||
if str(marker.get("icon", "")).strip_edges().is_empty():
|
||||
failures.append("move+attack marker should carry a generated icon hint: %s" % str(marker))
|
||||
if not found_auto_marker:
|
||||
failures.append("selected unit should mark enemies reachable by move+attack: %s" % str(auto_markers))
|
||||
scene.hover_cell = Vector2i(4, 3)
|
||||
@@ -587,6 +591,8 @@ func _check_scene_enemy_click_auto_attack(failures: Array[String]) -> void:
|
||||
failures.append("hovering a move+attack target should explain the combined action: %s" % str(auto_badge))
|
||||
if not ["advance", "counter", "danger"].has(str(auto_badge.get("kind", ""))):
|
||||
failures.append("move+attack hover badge should use an attack-risk color kind: %s" % str(auto_badge))
|
||||
if str(auto_badge.get("icon", "")).strip_edges().is_empty():
|
||||
failures.append("move+attack hover badge should carry a generated icon hint: %s" % str(auto_badge))
|
||||
|
||||
cao_cao["status_effects"] = [{
|
||||
"type": "action_lock",
|
||||
@@ -859,6 +865,8 @@ func _check_scene_post_move_tactic_picker_flow(failures: Array[String]) -> void:
|
||||
if tactic_markers.size() != 1 or tactic_markers[0].get("cell", Vector2i.ZERO) != Vector2i(3, 3):
|
||||
failures.append("tactic target markers should include only the Spark target: %s" % str(tactic_markers))
|
||||
else:
|
||||
if str(tactic_markers[0].get("icon", "")).strip_edges().is_empty():
|
||||
failures.append("tactic target marker should carry a generated icon hint: %s" % str(tactic_markers[0]))
|
||||
if str(tactic_markers[0].get("text", "")) != "퇴각":
|
||||
failures.append("tactic target marker should preview the deterministic KO: %s" % str(tactic_markers[0]))
|
||||
if str(tactic_markers[0].get("kind", "")) != "damage":
|
||||
@@ -957,6 +965,8 @@ func _check_scene_post_move_item_picker_flow(failures: Array[String]) -> void:
|
||||
if item_markers.size() != 1 or item_markers[0].get("cell", Vector2i.ZERO) != Vector2i(2, 4):
|
||||
failures.append("item target markers should include only the Bean target: %s" % str(item_markers))
|
||||
else:
|
||||
if str(item_markers[0].get("icon", "")).strip_edges().is_empty():
|
||||
failures.append("item target marker should carry a generated icon hint: %s" % str(item_markers[0]))
|
||||
if str(item_markers[0].get("text", "")) != "+20 병력":
|
||||
failures.append("item target marker should preview healing: %s" % str(item_markers[0]))
|
||||
if str(item_markers[0].get("kind", "")) != "heal":
|
||||
|
||||
@@ -4260,6 +4260,8 @@ func _check_hover_intent_badges(failures: Array[String]) -> void:
|
||||
var move_badge := scene._target_preview_badge()
|
||||
if str(move_badge.get("text", "")) != "행군" or str(move_badge.get("kind", "")) != "move":
|
||||
failures.append("reachable empty tile should show MOVE badge: %s" % str(move_badge))
|
||||
if str(move_badge.get("icon", "")) != "move":
|
||||
failures.append("MOVE badge should carry a generated icon hint: %s" % str(move_badge))
|
||||
if not scene._hover_info_badge().is_empty():
|
||||
failures.append("hover info badge should yield to active move intent badge")
|
||||
|
||||
@@ -4267,6 +4269,8 @@ func _check_hover_intent_badges(failures: Array[String]) -> void:
|
||||
var select_badge := scene._target_preview_badge()
|
||||
if str(select_badge.get("text", "")) != "선택" or str(select_badge.get("kind", "")) != "select":
|
||||
failures.append("friendly selectable unit should show SELECT badge: %s" % str(select_badge))
|
||||
if str(select_badge.get("icon", "")).strip_edges().is_empty():
|
||||
failures.append("SELECT badge should carry a generated icon hint: %s" % str(select_badge))
|
||||
|
||||
scene.hover_cell = Vector2i(1, 5)
|
||||
var attack_badge := scene._target_preview_badge()
|
||||
@@ -4276,6 +4280,8 @@ func _check_hover_intent_badges(failures: Array[String]) -> void:
|
||||
failures.append("adjacent attack badge should expose counterattack risk: %s" % str(attack_badge))
|
||||
if str(attack_badge.get("kind", "")) != "counter" and str(attack_badge.get("kind", "")) != "danger":
|
||||
failures.append("counterable attack badge should use a counter danger color kind: %s" % str(attack_badge))
|
||||
if str(attack_badge.get("icon", "")) != "threat":
|
||||
failures.append("counterable attack badge should carry a threat icon hint: %s" % str(attack_badge))
|
||||
|
||||
var cao_cao: Dictionary = scene.state.get_unit("cao_cao")
|
||||
cao_cao["moved"] = true
|
||||
@@ -4302,6 +4308,8 @@ func _check_hover_intent_badges(failures: Array[String]) -> void:
|
||||
failures.append("edge target preview badge should stay inside visible board maximum: %s / %s" % [str(edge_badge_rect), str(board_rect)])
|
||||
if edge_badge_rect.size.x <= BattleSceneScript.TILE_SIZE:
|
||||
failures.append("target preview badge should remain wide enough for Korean tactical text")
|
||||
if edge_badge_rect.size.x < 120.0:
|
||||
failures.append("target preview badge should reserve room for icon and Korean text")
|
||||
|
||||
scene.state.clear_selection()
|
||||
scene.selected_skill_id = ""
|
||||
|
||||
Reference in New Issue
Block a user