Add visual post-move picker rows

This commit is contained in:
2026-06-20 05:43:48 +09:00
parent 197b3cb10d
commit 059f79b0e4
3 changed files with 132 additions and 11 deletions

View File

@@ -35,6 +35,7 @@ Godot 4 tactical RPG prototype inspired by classic turn-based Romance of the Thr
- Pre-battle Formation lets deployed officers swap starting positions inside scenario-defined cells, with portrait rows, readable zone badges, and hover placement instructions. - Pre-battle Formation lets deployed officers swap starting positions inside scenario-defined cells, with portrait rows, readable zone badges, and hover placement instructions.
- 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 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. - 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.
- 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, 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. - 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. - 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.

View File

@@ -183,6 +183,8 @@ const POST_MOVE_TITLE_SIZE := Vector2(196, 22)
const POST_MOVE_PICKER_PANEL_SIZE := Vector2(336, 236) const POST_MOVE_PICKER_PANEL_SIZE := Vector2(336, 236)
const POST_MOVE_PICKER_TITLE_SIZE := Vector2(296, 22) const POST_MOVE_PICKER_TITLE_SIZE := Vector2(296, 22)
const POST_MOVE_PICKER_DETAIL_SIZE := Vector2(296, 32) 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 TARGETING_HINT_PANEL_SIZE := Vector2(254, 118) const TARGETING_HINT_PANEL_SIZE := Vector2(254, 118)
const TARGETING_HINT_TITLE_SIZE := Vector2(220, 22) const TARGETING_HINT_TITLE_SIZE := Vector2(220, 22)
const TARGETING_HINT_DETAIL_SIZE := Vector2(220, 28) const TARGETING_HINT_DETAIL_SIZE := Vector2(220, 28)
@@ -14910,12 +14912,8 @@ func _rebuild_post_move_tactic_picker(selected: Dictionary) -> void:
for skill_id_value in skill_ids: for skill_id_value in skill_ids:
var skill_id := str(skill_id_value) var skill_id := str(skill_id_value)
var skill := state.get_skill_def(skill_id) var skill := state.get_skill_def(skill_id)
var skill_button := Button.new()
skill_button.text = _format_post_move_tactic_button_text(skill_id, skill, selected)
var disabled_reason := _post_move_tactic_disabled_reason(selected, skill_id, skill) var disabled_reason := _post_move_tactic_disabled_reason(selected, skill_id, skill)
skill_button.disabled = not disabled_reason.is_empty() var skill_button := _make_post_move_tactic_option_button(skill_id, skill, disabled_reason)
_suppress_local_command_tooltip(skill_button)
_apply_button_style(skill_button)
skill_button.pressed.connect(_on_post_move_tactic_option_pressed.bind(skill_id)) skill_button.pressed.connect(_on_post_move_tactic_option_pressed.bind(skill_id))
post_move_picker_list.add_child(skill_button) post_move_picker_list.add_child(skill_button)
@@ -14933,17 +14931,115 @@ func _rebuild_post_move_item_picker(selected: Dictionary) -> void:
var item_id := str(item_id_value) var item_id := str(item_id_value)
var item := state.get_item_def(item_id) var item := state.get_item_def(item_id)
var count := int(inventory.get(item_id, 0)) var count := int(inventory.get(item_id, 0))
var item_button := Button.new()
item_button.text = _format_post_move_item_button_text(item_id, item, count)
_apply_item_button_icon(item_button, item)
var disabled_reason := _post_move_item_disabled_reason(selected, item_id, count) var disabled_reason := _post_move_item_disabled_reason(selected, item_id, count)
item_button.disabled = not disabled_reason.is_empty() var item_button := _make_post_move_item_option_button(item_id, item, count, disabled_reason)
_suppress_local_command_tooltip(item_button)
_apply_button_style(item_button)
item_button.pressed.connect(_on_post_move_item_option_pressed.bind(item_id)) item_button.pressed.connect(_on_post_move_item_option_pressed.bind(item_id))
post_move_picker_list.add_child(item_button) post_move_picker_list.add_child(item_button)
func _make_post_move_tactic_option_button(skill_id: String, skill: Dictionary, disabled_reason: String) -> Button:
var skill_name := str(skill.get("name", skill_id))
var mp_cost := int(skill.get("mp_cost", 0))
var kind_text := _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 detail_text := "%s · 거리 %s" % [kind_text, range_text]
if not effect_text.is_empty():
detail_text = "%s · %s" % [detail_text, effect_text]
if not disabled_reason.is_empty():
detail_text = "%s · %s" % [detail_text, _post_move_disabled_summary(disabled_reason)]
var button := Button.new()
button.text = "%s 기력 %d\n%s" % [skill_name, mp_cost, detail_text]
button.tooltip_text = _format_post_move_tactic_option_tooltip(skill_name, mp_cost, kind_text, range_text, effect_text, disabled_reason)
button.disabled = not disabled_reason.is_empty()
button.custom_minimum_size = POST_MOVE_PICKER_OPTION_SIZE
button.icon = _post_move_tactic_icon_texture(skill)
button.expand_icon = false
button.add_theme_constant_override("icon_max_width", POST_MOVE_PICKER_OPTION_ICON_MAX_WIDTH)
button.set_meta("picker_icon", _post_move_tactic_icon_kind(skill))
button.set_meta("picker_label", skill_name)
_apply_button_style(button)
return button
func _make_post_move_item_option_button(item_id: String, item: Dictionary, count: int, disabled_reason: String) -> Button:
var item_name := _item_display_name(item_id)
var effect_text := _format_post_move_item_effect_text(item)
var detail_text := "잔량 %d · %s" % [count, effect_text]
if not disabled_reason.is_empty():
detail_text = "%s · %s" % [detail_text, _post_move_disabled_summary(disabled_reason)]
var button := Button.new()
button.text = "%s\n%s" % [item_name, detail_text]
button.tooltip_text = _format_post_move_item_option_tooltip(item_name, count, effect_text, disabled_reason)
button.disabled = not disabled_reason.is_empty()
button.custom_minimum_size = POST_MOVE_PICKER_OPTION_SIZE
button.icon = _post_move_item_icon_texture(item)
button.expand_icon = false
button.add_theme_constant_override("icon_max_width", POST_MOVE_PICKER_OPTION_ICON_MAX_WIDTH)
button.set_meta("picker_icon", "item")
button.set_meta("picker_label", item_name)
_apply_button_style(button)
return button
func _post_move_tactic_icon_kind(skill: Dictionary) -> String:
var kind := str(skill.get("kind", "skill")).strip_edges().to_lower()
if kind == "damage":
return "attack"
if kind == "heal":
return "item"
if kind == "support":
return "status"
return "tactic"
func _post_move_tactic_icon_texture(skill: Dictionary) -> Texture2D:
return _make_toolbar_icon(_post_move_tactic_icon_kind(skill))
func _post_move_item_icon_texture(item: Dictionary) -> Texture2D:
var icon := _load_art_texture(str(item.get("icon", "")))
if icon != null:
return icon
return _make_toolbar_icon("item")
func _format_post_move_tactic_option_tooltip(skill_name: String, mp_cost: int, kind_text: String, range_text: String, effect_text: String, disabled_reason: String) -> String:
var lines: Array[String] = [
skill_name,
"기력 %d · %s · 사거리 %s" % [mp_cost, kind_text, range_text]
]
if not effect_text.is_empty():
lines.append(effect_text)
if not disabled_reason.is_empty():
lines.append(disabled_reason)
return _join_strings(lines, "\n")
func _format_post_move_item_option_tooltip(item_name: String, count: int, effect_text: String, disabled_reason: String) -> String:
var lines: Array[String] = [
item_name,
"잔량 %d · %s" % [count, effect_text]
]
if not disabled_reason.is_empty():
lines.append(disabled_reason)
return _join_strings(lines, "\n")
func _post_move_disabled_summary(disabled_reason: String) -> String:
if disabled_reason.contains("기력"):
return "기력 부족"
if disabled_reason.contains("봉인"):
return "봉인"
if disabled_reason.contains("대상"):
return "표식 없음"
if disabled_reason.contains("보급") or disabled_reason.contains("없습니다"):
return "잔량 없음"
return "불가"
func _format_post_move_tactic_button_text(skill_id: String, skill: Dictionary, selected: Dictionary) -> String: func _format_post_move_tactic_button_text(skill_id: String, skill: Dictionary, selected: Dictionary) -> String:
var marker := "> " if selected_skill_id == skill_id else "" var marker := "> " if selected_skill_id == skill_id else ""
var skill_name := str(skill.get("name", skill_id)) var skill_name := str(skill.get("name", skill_id))

View File

@@ -832,6 +832,7 @@ func _check_scene_post_move_tactic_picker_flow(failures: Array[String]) -> void:
if scene.tactic_menu != null and scene.tactic_menu.visible: if scene.tactic_menu != null and scene.tactic_menu.visible:
failures.append("post-move tactic shortcut should not use the side tactic menu") failures.append("post-move tactic shortcut should not use the side tactic menu")
_check_post_move_picker_option_tooltips_present(failures, scene, "post-move tactic picker") _check_post_move_picker_option_tooltips_present(failures, scene, "post-move tactic picker")
_check_post_move_picker_option_visuals(failures, scene, "post-move tactic picker")
var escape := InputEventKey.new() var escape := InputEventKey.new()
escape.keycode = KEY_ESCAPE escape.keycode = KEY_ESCAPE
@@ -941,6 +942,7 @@ func _check_scene_post_move_item_picker_flow(failures: Array[String]) -> void:
if scene.item_menu != null and scene.item_menu.visible: if scene.item_menu != null and scene.item_menu.visible:
failures.append("post-move item shortcut should not use the side item menu") failures.append("post-move item shortcut should not use the side item menu")
_check_post_move_picker_option_tooltips_present(failures, scene, "post-move item picker") _check_post_move_picker_option_tooltips_present(failures, scene, "post-move item picker")
_check_post_move_picker_option_visuals(failures, scene, "post-move item picker")
scene._on_post_move_item_option_pressed("bean") scene._on_post_move_item_option_pressed("bean")
if scene.selected_item_id != "bean": if scene.selected_item_id != "bean":
@@ -1027,6 +1029,28 @@ func _check_post_move_picker_option_tooltips_present(failures: Array[String], sc
failures.append("%s option should explain its function through hover tooltip" % label) failures.append("%s option should explain its function through hover tooltip" % label)
func _check_post_move_picker_option_visuals(failures: Array[String], scene, label: String) -> void:
if scene.post_move_picker_list == null:
failures.append("%s should expose a visual picker option list" % label)
return
var saw_button := false
for child in scene.post_move_picker_list.get_children():
if not child is Button:
continue
saw_button = true
var button := child as Button
if button.icon == null or str(button.get_meta("picker_icon", "")).strip_edges().is_empty():
failures.append("%s option should carry an immediate generated icon" % label)
if button.custom_minimum_size.x < BattleSceneScript.POST_MOVE_PICKER_OPTION_SIZE.x or button.custom_minimum_size.y < BattleSceneScript.POST_MOVE_PICKER_OPTION_SIZE.y:
failures.append("%s option should reserve stable visual row space: %s" % [label, str(button.custom_minimum_size)])
if not button.text.contains("\n"):
failures.append("%s option should use compact two-line visual text: %s" % [label, button.text])
if button.get_theme_constant("icon_max_width") < BattleSceneScript.POST_MOVE_PICKER_OPTION_ICON_MAX_WIDTH:
failures.append("%s option should reserve readable icon width" % label)
if not saw_button:
failures.append("%s should create at least one visual option button" % label)
func _check_fitted_label_font(failures: Array[String], label: Label, base_size: int, min_size: int, label_name: String) -> void: func _check_fitted_label_font(failures: Array[String], label: Label, base_size: int, min_size: int, label_name: String) -> void:
var font_size := label.get_theme_font_size("font_size") var font_size := label.get_theme_font_size("font_size")
if font_size < min_size or font_size > base_size: if font_size < min_size or font_size > base_size: