Add portrait formation rows

This commit is contained in:
2026-06-20 00:19:46 +09:00
parent d1f33c6dcb
commit 18b073f937
3 changed files with 82 additions and 16 deletions

View File

@@ -24,7 +24,7 @@ Godot 4 tactical RPG prototype inspired by classic turn-based Romance of the Thr
- Pre-battle Talk can list scenario camp conversations by officer or topic, branch them from saved campaign choices, grant one-time pre-battle supplies, and shop stock can include merchant flavor lines before a battle.
- Pre-battle Armory equipment changes, including unequipping gear back into stock, save immediately and sync roster equipment plus inventory stock, with portrait and item-icon rows plus hover change details.
- Pre-battle Roster can move optional officers between sortie and reserve within scenario limits, using portrait rows, compact status badges, and hover details.
- Pre-battle Formation lets deployed officers swap starting positions inside scenario-defined cells.
- Pre-battle Formation lets deployed officers swap starting positions inside scenario-defined cells, with portrait rows, coordinate badges, and hover placement instructions.
- Pre-battle briefing shows the campaign chapter, chapter battle number, location, victory/defeat summary, and first-clear reward preview.
- Pre-battle Chapters overview shows story-arc progress and can open completed or current battles.
- Pre-battle Save menu can write and load one manual campaign checkpoint separate from the automatic save.

View File

@@ -11381,11 +11381,10 @@ func _rebuild_formation_menu() -> void:
var selected := state.get_unit(formation_unit_id)
if selected.is_empty():
formation_status_label.text = "진형"
formation_status_label.tooltip_text = "출진 장수를 고른 뒤 전장도의 금빛 진형 칸을 클릭해 배치합니다."
else:
formation_status_label.text = "진형: %s · %s" % [
str(selected.get("name", "장수")),
_format_cell_label(selected.get("pos", Vector2i.ZERO))
]
formation_status_label.text = "진형: %s" % str(selected.get("name", "장수"))
formation_status_label.tooltip_text = _format_formation_unit_tooltip(selected, true)
if units.is_empty():
var empty_label := Label.new()
@@ -11396,12 +11395,7 @@ func _rebuild_formation_menu() -> void:
for unit in units:
var unit_id := str(unit.get("id", ""))
var unit_button := Button.new()
unit_button.text = _format_formation_unit_button_text(unit)
_apply_button_style(unit_button)
unit_button.disabled = unit_id == formation_unit_id
unit_button.pressed.connect(_on_formation_unit_pressed.bind(unit_id))
formation_list.add_child(unit_button)
_add_formation_unit_row(unit, unit_id == formation_unit_id)
func _clear_formation_list() -> void:
@@ -11411,14 +11405,73 @@ func _clear_formation_list() -> void:
func _format_formation_unit_button_text(unit: Dictionary) -> String:
var marker := "> " if str(unit.get("id", "")) == formation_unit_id else ""
return "%s%s %s" % [
marker,
str(unit.get("name", "장수")),
_format_cell_label(unit.get("pos", Vector2i.ZERO))
return str(unit.get("name", "장수"))
func _add_formation_unit_row(unit: Dictionary, selected: bool) -> void:
var unit_id := str(unit.get("id", ""))
var tooltip_text := _format_formation_unit_tooltip(unit, selected)
var row := HBoxContainer.new()
row.custom_minimum_size = Vector2(580, 62)
row.add_theme_constant_override("separation", 8)
row.tooltip_text = tooltip_text
formation_list.add_child(row)
row.add_child(_make_roster_unit_portrait(unit, tooltip_text))
var column := VBoxContainer.new()
column.size_flags_horizontal = Control.SIZE_EXPAND_FILL
column.add_theme_constant_override("separation", 4)
column.tooltip_text = tooltip_text
row.add_child(column)
var unit_button := Button.new()
unit_button.text = _format_formation_unit_button_text(unit)
unit_button.custom_minimum_size = Vector2(500, 30)
unit_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
unit_button.disabled = selected
unit_button.tooltip_text = tooltip_text
_apply_button_style(unit_button)
unit_button.pressed.connect(_on_formation_unit_pressed.bind(unit_id))
column.add_child(unit_button)
var badge_row := HBoxContainer.new()
badge_row.custom_minimum_size = Vector2(500, 22)
badge_row.add_theme_constant_override("separation", 5)
badge_row.tooltip_text = tooltip_text
column.add_child(badge_row)
for badge_text in _format_formation_unit_badges(unit, selected):
var text := str(badge_text).strip_edges()
if text.is_empty():
continue
badge_row.add_child(_make_armory_info_badge(text, tooltip_text))
func _format_formation_unit_badges(unit: Dictionary, selected: bool) -> Array:
return [
"선택" if selected else "대상",
"좌표 %s" % _format_cell_label(unit.get("pos", Vector2i.ZERO)),
_unit_class_display_name(unit)
]
func _format_formation_unit_tooltip(unit: Dictionary, selected: bool) -> String:
var lines := [
"%s · 품계 %d · %s" % [
str(unit.get("name", "장수")),
int(unit.get("level", 1)),
_unit_class_display_name(unit)
],
"현재 위치: %s" % _format_cell_label(unit.get("pos", Vector2i.ZERO))
]
if selected:
lines.append("전장도의 금빛 진형 칸을 클릭하면 이 장수를 옮깁니다.")
else:
lines.append("클릭하면 배치할 장수로 선택합니다.")
return _join_strings(lines, "\n")
func _format_cell_label(value) -> String:
if typeof(value) == TYPE_VECTOR2I:
return "%d,%d" % [value.x + 1, value.y + 1]

View File

@@ -3008,6 +3008,19 @@ func _check_scene_texture_loading(failures: Array[String]) -> void:
failures.append("shop row tooltip should retain full item detail: %s" % shop_tooltips)
if not _has_descendant_texture(scene.shop_list):
failures.append("shop rows should use item icon artwork")
scene._rebuild_formation_menu()
var formation_visible_text := _collect_child_text(scene.formation_list)
var formation_tooltips := _collect_child_tooltips(scene.formation_list)
if not formation_visible_text.contains("조조") or not formation_visible_text.contains("좌표") or not formation_visible_text.contains("선택"):
failures.append("formation rows should show officer name and compact placement badges: %s" % formation_visible_text)
if formation_visible_text.contains("전장도의") or formation_visible_text.contains("클릭하면"):
failures.append("formation rows should keep placement instructions in hover text: %s" % formation_visible_text)
if not formation_tooltips.contains("금빛 진형 칸") or not formation_tooltips.contains("현재 위치"):
failures.append("formation row tooltip should retain placement instructions: %s" % formation_tooltips)
if not _has_descendant_texture(scene.formation_list):
failures.append("formation rows should reuse officer portrait artwork")
if scene._format_formation_unit_button_text(scene.state.get_unit("cao_cao")).contains(","):
failures.append("formation unit button text should stay compact")
scene.free()
_check_scenario_background_texture_loading(failures)