Compact chapter overview rows
This commit is contained in:
@@ -28,7 +28,7 @@ Godot 4 tactical RPG prototype inspired by classic turn-based Romance of the Thr
|
||||
- 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, 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 Chapters overview shows story-arc progress with map thumbnail rows, compact status badges, hover details, and can open completed or current battles.
|
||||
- Pre-battle Save menu can write and load one manual campaign checkpoint separate from the automatic save.
|
||||
- The title screen exposes Start, Load, and Settings, and Load now lets the player choose between the automatic record and the manual checkpoint.
|
||||
- Victory results show compact reward icons for gold, items, and officer roster changes, with hover details for the full reward text.
|
||||
|
||||
@@ -120,6 +120,9 @@ const LOCAL_COMMAND_TITLE_MIN_FONT_SIZE := 10
|
||||
const LOCAL_COMMAND_DETAIL_FONT_SIZE := 12
|
||||
const LOCAL_COMMAND_DETAIL_MIN_FONT_SIZE := 10
|
||||
const BRIEFING_CAMP_THUMBNAIL_SIZE := Vector2(188, 86)
|
||||
const CHAPTER_SEAL_BADGE_SIZE := Vector2(54, 54)
|
||||
const CHAPTER_SCENARIO_THUMBNAIL_SIZE := Vector2(72, 50)
|
||||
const CHAPTER_INFO_BADGE_SIZE := Vector2(86, 22)
|
||||
const CAMP_TALK_PORTRAIT_SIZE := Vector2(58, 58)
|
||||
const SHOP_MERCHANT_BADGE_SIZE := Vector2(50, 50)
|
||||
const SHOP_ITEM_ICON_SIZE := Vector2(48, 48)
|
||||
@@ -9870,7 +9873,7 @@ func _rebuild_chapter_menu() -> void:
|
||||
child.queue_free()
|
||||
var entries := campaign_state.get_chapter_progress_entries()
|
||||
if chapter_status_label != null:
|
||||
chapter_status_label.text = "%s 병서 · 봉인 전장 %d/%d" % [
|
||||
chapter_status_label.text = "%s · 진행 %d/%d" % [
|
||||
campaign_state.campaign_title,
|
||||
campaign_state.completed_scenarios.size(),
|
||||
campaign_state.get_scenario_count()
|
||||
@@ -9882,55 +9885,231 @@ func _rebuild_chapter_menu() -> void:
|
||||
chapter_list.add_child(empty_label)
|
||||
return
|
||||
for entry in entries:
|
||||
var completed := int(entry.get("completed", 0))
|
||||
var total := int(entry.get("total", 0))
|
||||
var current := bool(entry.get("current", false))
|
||||
var status := "현 전장" if current else ("봉인 완료" if total > 0 and completed >= total else "미개봉")
|
||||
var current_title := str(entry.get("current_scenario_title", ""))
|
||||
var line := "병서 %02d/%02d · %s [%s] %d/%d" % [
|
||||
int(entry.get("position", 0)),
|
||||
int(entry.get("chapter_count", 0)),
|
||||
entry.get("title", ""),
|
||||
status,
|
||||
completed,
|
||||
total
|
||||
]
|
||||
if current and not current_title.is_empty():
|
||||
line += "\n현 전장: %s" % current_title
|
||||
var chapter_label := Label.new()
|
||||
chapter_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
chapter_label.custom_minimum_size = Vector2(560, 0)
|
||||
chapter_label.text = line
|
||||
_apply_label_style(chapter_label, UI_AGED_INK)
|
||||
chapter_list.add_child(chapter_label)
|
||||
for scenario in entry.get("scenarios", []):
|
||||
_add_chapter_scenario_entry(scenario)
|
||||
_add_chapter_progress_entry(entry)
|
||||
if bool(entry.get("current", false)) or int(entry.get("completed", 0)) > 0:
|
||||
for scenario in entry.get("scenarios", []):
|
||||
_add_chapter_scenario_entry(scenario)
|
||||
|
||||
|
||||
func _add_chapter_progress_entry(entry: Dictionary) -> void:
|
||||
var tooltip_text := _format_chapter_progress_tooltip(entry)
|
||||
var row := HBoxContainer.new()
|
||||
row.custom_minimum_size = Vector2(580, 58)
|
||||
row.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
row.add_theme_constant_override("separation", 8)
|
||||
row.tooltip_text = tooltip_text
|
||||
chapter_list.add_child(row)
|
||||
|
||||
row.add_child(_make_initials_badge(str(entry.get("title", "병서")), CHAPTER_SEAL_BADGE_SIZE, 16))
|
||||
|
||||
var column := VBoxContainer.new()
|
||||
column.custom_minimum_size = Vector2(518, 0)
|
||||
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 title_label := Label.new()
|
||||
title_label.custom_minimum_size = Vector2(518, 26)
|
||||
title_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
title_label.text = "%02d/%02d %s" % [
|
||||
int(entry.get("position", 0)),
|
||||
int(entry.get("chapter_count", 0)),
|
||||
str(entry.get("title", ""))
|
||||
]
|
||||
title_label.tooltip_text = tooltip_text
|
||||
_apply_label_style(title_label, UI_AGED_INK, 13)
|
||||
column.add_child(title_label)
|
||||
|
||||
var badge_row := HBoxContainer.new()
|
||||
badge_row.custom_minimum_size = Vector2(518, 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_chapter_progress_badges(entry):
|
||||
var text := str(badge_text).strip_edges()
|
||||
if text.is_empty():
|
||||
continue
|
||||
badge_row.add_child(_make_chapter_info_badge(text, tooltip_text))
|
||||
|
||||
|
||||
func _add_chapter_scenario_entry(scenario: Dictionary) -> void:
|
||||
var scenario_id := str(scenario.get("id", ""))
|
||||
var title := str(scenario.get("title", scenario_id))
|
||||
var position := int(scenario.get("position", 0))
|
||||
var total := int(scenario.get("total", 0))
|
||||
var completed := bool(scenario.get("completed", false))
|
||||
var current := bool(scenario.get("current", false))
|
||||
var available := bool(scenario.get("available", false))
|
||||
var status := "현 전장" if current else ("회상 가능" if completed else "미개봉")
|
||||
var text := " %02d/%02d %s [%s]" % [position, total, title, status]
|
||||
var tooltip_text := _format_chapter_scenario_tooltip(scenario)
|
||||
var row := HBoxContainer.new()
|
||||
row.custom_minimum_size = Vector2(580, 58)
|
||||
row.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
row.add_theme_constant_override("separation", 8)
|
||||
row.tooltip_text = tooltip_text
|
||||
chapter_list.add_child(row)
|
||||
|
||||
row.add_child(_make_chapter_scenario_thumbnail(scenario, tooltip_text))
|
||||
|
||||
var column := VBoxContainer.new()
|
||||
column.custom_minimum_size = Vector2(500, 0)
|
||||
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 scenario_button := Button.new()
|
||||
scenario_button.custom_minimum_size = Vector2(500, 28)
|
||||
scenario_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
scenario_button.text = title
|
||||
scenario_button.tooltip_text = tooltip_text
|
||||
scenario_button.disabled = not available
|
||||
scenario_button.add_theme_font_size_override("font_size", 12)
|
||||
_apply_button_style(scenario_button)
|
||||
if available:
|
||||
var scenario_button := Button.new()
|
||||
scenario_button.custom_minimum_size = Vector2(560, 0)
|
||||
scenario_button.text = text
|
||||
_apply_button_style(scenario_button)
|
||||
scenario_button.pressed.connect(_on_chapter_scenario_pressed.bind(scenario_id))
|
||||
chapter_list.add_child(scenario_button)
|
||||
column.add_child(scenario_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_chapter_scenario_badges(scenario):
|
||||
var text := str(badge_text).strip_edges()
|
||||
if text.is_empty():
|
||||
continue
|
||||
badge_row.add_child(_make_chapter_info_badge(text, tooltip_text))
|
||||
|
||||
|
||||
func _format_chapter_progress_badges(entry: Dictionary) -> Array:
|
||||
var completed := int(entry.get("completed", 0))
|
||||
var total := int(entry.get("total", 0))
|
||||
return [
|
||||
_chapter_progress_status_text(entry),
|
||||
"전장 %d/%d" % [completed, total]
|
||||
]
|
||||
|
||||
|
||||
func _format_chapter_scenario_badges(scenario: Dictionary) -> Array:
|
||||
return [
|
||||
"%02d/%02d" % [int(scenario.get("position", 0)), int(scenario.get("total", 0))],
|
||||
_chapter_scenario_status_text(scenario),
|
||||
"열람" if bool(scenario.get("available", false)) else "잠김"
|
||||
]
|
||||
|
||||
|
||||
func _chapter_progress_status_text(entry: Dictionary) -> String:
|
||||
var completed := int(entry.get("completed", 0))
|
||||
var total := int(entry.get("total", 0))
|
||||
if bool(entry.get("current", false)):
|
||||
return "진행 중"
|
||||
if total > 0 and completed >= total:
|
||||
return "완료"
|
||||
return "미개방"
|
||||
|
||||
|
||||
func _chapter_scenario_status_text(scenario: Dictionary) -> String:
|
||||
if bool(scenario.get("current", false)):
|
||||
return "현 전장"
|
||||
if bool(scenario.get("completed", false)):
|
||||
return "회상"
|
||||
return "미개방"
|
||||
|
||||
|
||||
func _format_chapter_progress_tooltip(entry: Dictionary) -> String:
|
||||
var lines := [
|
||||
"병서 %02d/%02d · %s" % [
|
||||
int(entry.get("position", 0)),
|
||||
int(entry.get("chapter_count", 0)),
|
||||
str(entry.get("title", ""))
|
||||
],
|
||||
"상태: %s" % _chapter_progress_status_text(entry),
|
||||
"전장 진행: %d/%d" % [int(entry.get("completed", 0)), int(entry.get("total", 0))]
|
||||
]
|
||||
var current_title := str(entry.get("current_scenario_title", "")).strip_edges()
|
||||
if bool(entry.get("current", false)) and not current_title.is_empty():
|
||||
lines.append("현 전장: %s" % current_title)
|
||||
return _join_strings(lines, "\n")
|
||||
|
||||
|
||||
func _format_chapter_scenario_tooltip(scenario: Dictionary) -> String:
|
||||
var title := str(scenario.get("title", scenario.get("id", "")))
|
||||
var status := _chapter_scenario_status_text(scenario)
|
||||
var lines := [
|
||||
"%02d/%02d · %s" % [int(scenario.get("position", 0)), int(scenario.get("total", 0)), title],
|
||||
"상태: %s" % status
|
||||
]
|
||||
if bool(scenario.get("current", false)):
|
||||
lines.append("클릭하면 이 전장의 군막으로 이동합니다.")
|
||||
elif bool(scenario.get("completed", false)):
|
||||
lines.append("클릭하면 회상 전투를 엽니다.")
|
||||
else:
|
||||
var scenario_label := Label.new()
|
||||
scenario_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
scenario_label.custom_minimum_size = Vector2(560, 0)
|
||||
scenario_label.text = text
|
||||
_apply_label_style(scenario_label, UI_AGED_INK)
|
||||
chapter_list.add_child(scenario_label)
|
||||
lines.append("앞 전장을 마치면 열립니다.")
|
||||
return _join_strings(lines, "\n")
|
||||
|
||||
|
||||
func _make_chapter_info_badge(text: String, tooltip_text: String) -> PanelContainer:
|
||||
var panel := PanelContainer.new()
|
||||
panel.custom_minimum_size = CHAPTER_INFO_BADGE_SIZE
|
||||
panel.tooltip_text = tooltip_text
|
||||
_apply_panel_style(panel, "caption")
|
||||
|
||||
var label := Label.new()
|
||||
label.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
label.text = text
|
||||
label.tooltip_text = tooltip_text
|
||||
_apply_label_style(label, UI_PARCHMENT_TEXT, 10)
|
||||
panel.add_child(label)
|
||||
return panel
|
||||
|
||||
|
||||
func _make_chapter_scenario_thumbnail(scenario: Dictionary, tooltip_text: String) -> PanelContainer:
|
||||
var panel := PanelContainer.new()
|
||||
panel.custom_minimum_size = CHAPTER_SCENARIO_THUMBNAIL_SIZE
|
||||
panel.tooltip_text = tooltip_text
|
||||
_apply_panel_style(panel, "map")
|
||||
|
||||
var stack := Control.new()
|
||||
stack.custom_minimum_size = CHAPTER_SCENARIO_THUMBNAIL_SIZE
|
||||
stack.tooltip_text = tooltip_text
|
||||
panel.add_child(stack)
|
||||
|
||||
var texture_rect := TextureRect.new()
|
||||
texture_rect.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
texture_rect.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||||
texture_rect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_COVERED
|
||||
texture_rect.modulate = Color(0.92, 0.82, 0.62, 0.96)
|
||||
texture_rect.tooltip_text = tooltip_text
|
||||
var texture := _load_art_texture(_chapter_scenario_background_path(str(scenario.get("id", ""))))
|
||||
texture_rect.texture = texture
|
||||
texture_rect.visible = texture != null
|
||||
stack.add_child(texture_rect)
|
||||
|
||||
var fallback_label := Label.new()
|
||||
fallback_label.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
fallback_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
fallback_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
fallback_label.text = _dialogue_portrait_initials(str(scenario.get("title", scenario.get("id", "")))) if texture == null else ""
|
||||
fallback_label.visible = texture == null
|
||||
fallback_label.tooltip_text = tooltip_text
|
||||
_apply_label_style(fallback_label, UI_OLD_BRONZE, 16)
|
||||
stack.add_child(fallback_label)
|
||||
stack.add_child(_make_map_lattice_overlay(CHAPTER_SCENARIO_THUMBNAIL_SIZE))
|
||||
return panel
|
||||
|
||||
|
||||
func _chapter_scenario_background_path(scenario_id: String) -> String:
|
||||
var scenario_path := campaign_state.get_scenario_path(scenario_id)
|
||||
if scenario_path.is_empty() or not FileAccess.file_exists(scenario_path):
|
||||
return ""
|
||||
var parsed = JSON.parse_string(FileAccess.get_file_as_string(scenario_path))
|
||||
if typeof(parsed) != TYPE_DICTIONARY:
|
||||
return ""
|
||||
var map_data = parsed.get("map", {})
|
||||
if typeof(map_data) != TYPE_DICTIONARY:
|
||||
return ""
|
||||
return str(map_data.get("background", ""))
|
||||
|
||||
|
||||
func _on_chapter_scenario_pressed(scenario_id: String) -> void:
|
||||
|
||||
@@ -2994,6 +2994,8 @@ func _check_scene_texture_loading(failures: Array[String]) -> void:
|
||||
)
|
||||
if not bean_tooltip.contains("\n") or not bean_tooltip.contains("보유 0점"):
|
||||
failures.append("shop item tooltip should preserve full item detail")
|
||||
scene.campaign_state.load_campaign(BattleSceneScript.CAMPAIGN_PATH)
|
||||
scene.campaign_state.start_new("001_yellow_turbans")
|
||||
scene._create_hud()
|
||||
scene.campaign_state.gold = 320
|
||||
scene.shop_sell_mode = false
|
||||
@@ -3032,6 +3034,22 @@ func _check_scene_texture_loading(failures: Array[String]) -> void:
|
||||
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._rebuild_chapter_menu()
|
||||
var chapter_status_text: String = scene.chapter_status_label.text if scene.chapter_status_label != null else ""
|
||||
var chapter_visible_text := _collect_child_text(scene.chapter_list)
|
||||
var chapter_tooltips := _collect_child_tooltips(scene.chapter_list)
|
||||
if not chapter_status_text.contains("조조전") or not chapter_status_text.contains("진행"):
|
||||
failures.append("chapter status should stay compact and campaign-aware: %s" % chapter_status_text)
|
||||
if not chapter_visible_text.contains("난세의 기치") or not chapter_visible_text.contains("영천 소전") or not chapter_visible_text.contains("현 전장"):
|
||||
failures.append("chapter rows should show compact titles and state badges: %s" % chapter_visible_text)
|
||||
if chapter_visible_text.contains("[") or chapter_visible_text.contains("현 전장:") or chapter_visible_text.contains("앞 전장을 마치면"):
|
||||
failures.append("chapter rows should keep dense state detail in hover text: %s" % chapter_visible_text)
|
||||
if chapter_visible_text.contains("백마 구원전"):
|
||||
failures.append("chapter rows should not dump unopened future chapter battles: %s" % chapter_visible_text)
|
||||
if not chapter_tooltips.contains("현 전장: 영천 소전") or not chapter_tooltips.contains("클릭하면 이 전장의 군막으로 이동합니다.") or not chapter_tooltips.contains("앞 전장을 마치면 열립니다."):
|
||||
failures.append("chapter row tooltips should retain current and locked battle details: %s" % chapter_tooltips)
|
||||
if not _has_descendant_texture(scene.chapter_list):
|
||||
failures.append("chapter scenario rows should use battlefield thumbnail artwork")
|
||||
scene.free()
|
||||
|
||||
_check_scenario_background_texture_loading(failures)
|
||||
|
||||
Reference in New Issue
Block a user