diff --git a/README.md b/README.md index 9ca6459..e1b4a81 100644 --- a/README.md +++ b/README.md @@ -132,7 +132,7 @@ Godot 4 tactical RPG prototype inspired by classic turn-based Romance of the Thr - Campaign completion screen. - New campaign save reset. - Manual campaign checkpoint save/load. -- Cinematic title screen uses a dedicated ancient-war-camp dawn backdrop, Cao Cao portrait art, and a compact left-side command board with `처음부터`, `로드하기`, and `환경설정`; its generated command board, title buttons, and opening-story controls keep safer source-art proportions plus linear filtering for QHD fullscreen scaling. New campaigns then move into a generated four-panel Yellow Turban rebellion, Cao Cao resolve, Xiahou Dun meeting, and Yingchuan contact prologue with full-screen story artwork, a chapter seal, a compact Korean story-ledger strip instead of exposed numeric cards, and a bottom scroll caption panel. Title and in-game settings expose clearer background-music, combat-SFX, edge-scroll, and fullscreen display controls, include one-click audio recovery, and persist preferences in `user://heros_settings.json`. +- Cinematic title screen uses a dedicated ancient-war-camp dawn backdrop, Cao Cao portrait art, and a compact left-side command board with `처음부터`, `로드하기`, and `환경설정`; its generated command board, title buttons, and opening-story controls keep safer source-art proportions plus linear filtering for QHD fullscreen scaling. New campaigns then move into a generated four-panel Yellow Turban rebellion, Cao Cao resolve, Xiahou Dun meeting, and Yingchuan contact prologue with full-screen story artwork, a chapter seal, a compact chapter-side story-ledger strip instead of exposed numeric cards, QHD-safe record/camp buttons, and a bottom scroll caption panel. Title and in-game settings expose clearer background-music, combat-SFX, edge-scroll, and fullscreen display controls, include one-click audio recovery, and persist preferences in `user://heros_settings.json`. - Victory and defeat result overlay. - Dialogue portrait slot stays fixed across speaker and narration lines, with officer default image paths, optional per-line overrides, and speaker-initial or record fallback. - Initial AI-generated photorealistic officer portraits for Cao Cao, Xiahou Dun, Xiahou Yuan, Cao Ren, Dian Wei, Guo Jia, and Zhang He. diff --git a/scripts/scenes/battle_scene.gd b/scripts/scenes/battle_scene.gd index 013458e..fcefef0 100644 --- a/scripts/scenes/battle_scene.gd +++ b/scripts/scenes/battle_scene.gd @@ -168,8 +168,8 @@ const TITLE_STORY_PREVIEW_LABEL_SIZE := Vector2(140, 16) const TITLE_MENU_ICON_MAX_WIDTH := 42 const TITLE_MENU_BOARD_SIZE := Vector2(520, 650) const TITLE_MENU_PANEL_SIZE := Vector2(462, 536) -const OPENING_STORY_PROGRESS_LEDGER_SIZE := Vector2(356, 56) -const OPENING_STORY_PROGRESS_SEAL_SIZE := Vector2(38, 38) +const OPENING_STORY_PROGRESS_LEDGER_SIZE := Vector2(326, 48) +const OPENING_STORY_PROGRESS_SEAL_SIZE := Vector2(34, 34) const COMMAND_NOTICE_ICON_SIZE := Vector2(32, 32) const COMMAND_NOTICE_MAX_ICONS := 5 const CHAPTER_SEAL_BADGE_SIZE := Vector2(54, 54) @@ -1113,13 +1113,13 @@ func _text_button_texture_margin_for(button: Button, title_button: bool, opening var target := _control_texture_target_size(button, Vector2(148.0, 44.0)) var max_x := maxi(8, int(floor(target.x * 0.5)) - 3) var max_y := maxi(6, int(floor(target.y * 0.5)) - 3) - var desired_x := int(floor(target.x * (0.14 if not title_button else 0.12))) - var desired_y := int(floor(target.y * (0.22 if not opening_story_button else 0.18))) - var cap_x := 44 if opening_story_button else (60 if title_button else 72) - var cap_y := 12 if opening_story_button else (18 if title_button else 24) + var desired_x := int(floor(target.x * (0.16 if opening_story_button else (0.12 if title_button else 0.14)))) + var desired_y := int(floor(target.y * (0.24 if opening_story_button else 0.22))) + var cap_x := 38 if opening_story_button else (60 if title_button else 72) + var cap_y := 14 if opening_story_button else (18 if title_button else 24) return Vector2i( mini(max_x, maxi(16, mini(cap_x, desired_x))), - mini(max_y, maxi(8, mini(cap_y, desired_y))) + mini(max_y, maxi(10 if opening_story_button else 8, mini(cap_y, desired_y))) ) @@ -1138,7 +1138,7 @@ func _apply_generated_button_style(button: Button, important: bool = false) -> b var icon_only := bool(button.get_meta("icon_only", false)) var title_button := bool(button.get_meta("title_menu_button", false)) var opening_story_button := bool(button.get_meta("opening_story_button", false)) - var button_margin := 6 if opening_story_button else (8 if title_button else 10) + var button_margin := 8 if opening_story_button else (8 if title_button else 10) var button_texture_margin := _text_button_texture_margin_for(button, title_button, opening_story_button) var icon_texture_margin := _icon_button_texture_margin_for(button) var normal_style := _make_icon_button_texture_style("pressed" if important else "normal", button_margin, icon_texture_margin) if icon_only else _make_button_texture_style("pressed" if important else "normal", button_margin, button_texture_margin) @@ -2703,23 +2703,23 @@ func _make_title_story_preview_card(title: String, image_path: String, index: in func _make_opening_story_beat_strip() -> PanelContainer: var panel := PanelContainer.new() panel.name = "OpeningStoryLedgerStrip" - panel.position = Vector2(876, 22) + panel.position = Vector2(326, 26) panel.size = OPENING_STORY_PROGRESS_LEDGER_SIZE panel.custom_minimum_size = OPENING_STORY_PROGRESS_LEDGER_SIZE - panel.modulate = Color(1.0, 1.0, 1.0, 0.92) - panel.tooltip_text = "지금 펼쳐진 서문 기록입니다." - _apply_panel_style(panel, "caption") + panel.modulate = Color(1.0, 1.0, 1.0, 0.96) + panel.tooltip_text = "제1장 서문에서 지금 펼쳐진 기록입니다." + _apply_panel_style(panel, "transparent_overlay") var panel_style := panel.get_theme_stylebox("panel") if panel_style != null: - panel_style.content_margin_left = 9 - panel_style.content_margin_right = 9 - panel_style.content_margin_top = 8 - panel_style.content_margin_bottom = 8 + panel_style.content_margin_left = 0 + panel_style.content_margin_right = 0 + panel_style.content_margin_top = 0 + panel_style.content_margin_bottom = 0 var rail_row := HBoxContainer.new() rail_row.name = "OpeningStoryProgressRail" - rail_row.custom_minimum_size = Vector2(334, 40) - rail_row.add_theme_constant_override("separation", 9) + rail_row.custom_minimum_size = Vector2(318, 40) + rail_row.add_theme_constant_override("separation", 8) rail_row.tooltip_text = panel.tooltip_text panel.add_child(rail_row) @@ -2727,7 +2727,7 @@ func _make_opening_story_beat_strip() -> PanelContainer: seal.tooltip_text = panel.tooltip_text var seal_icon := TextureRect.new() seal_icon.name = "OpeningStoryProgressIcon" - seal_icon.custom_minimum_size = Vector2(24, 24) + seal_icon.custom_minimum_size = Vector2(22, 22) seal_icon.size_flags_vertical = Control.SIZE_SHRINK_CENTER seal_icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE seal_icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED @@ -2738,15 +2738,15 @@ func _make_opening_story_beat_strip() -> PanelContainer: var text_column := VBoxContainer.new() text_column.name = "OpeningStoryProgressText" - text_column.custom_minimum_size = Vector2(198, 40) + text_column.custom_minimum_size = Vector2(178, 40) text_column.add_theme_constant_override("separation", 2) text_column.tooltip_text = panel.tooltip_text rail_row.add_child(text_column) var rail_label := Label.new() rail_label.name = "OpeningStoryProgressCaption" - rail_label.text = "영천으로" - rail_label.custom_minimum_size = Vector2(196, 18) + rail_label.text = "제1장 영천으로" + rail_label.custom_minimum_size = Vector2(176, 18) rail_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_LEFT rail_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER rail_label.tooltip_text = panel.tooltip_text @@ -2755,7 +2755,7 @@ func _make_opening_story_beat_strip() -> PanelContainer: opening_prologue_progress_label = Label.new() opening_prologue_progress_label.name = "OpeningStoryProgressScene" - opening_prologue_progress_label.custom_minimum_size = Vector2(196, 20) + opening_prologue_progress_label.custom_minimum_size = Vector2(176, 20) opening_prologue_progress_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_LEFT opening_prologue_progress_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER opening_prologue_progress_label.tooltip_text = panel.tooltip_text @@ -2764,7 +2764,7 @@ func _make_opening_story_beat_strip() -> PanelContainer: opening_prologue_story_strip = HBoxContainer.new() opening_prologue_story_strip.name = "OpeningStoryLedgerMarks" - opening_prologue_story_strip.custom_minimum_size = Vector2(78, 18) + opening_prologue_story_strip.custom_minimum_size = Vector2(74, 18) opening_prologue_story_strip.size_flags_vertical = Control.SIZE_SHRINK_CENTER opening_prologue_story_strip.add_theme_constant_override("separation", 5) opening_prologue_story_strip.tooltip_text = panel.tooltip_text @@ -2789,7 +2789,7 @@ func _rebuild_opening_story_beat_strip() -> void: for index in range(page_count): var tick := ColorRect.new() tick.name = "OpeningStoryLedgerTick%d" % (index + 1) - tick.custom_minimum_size = Vector2(22, 5) if index == opening_prologue_index else Vector2(12, 4) + tick.custom_minimum_size = Vector2(20, 5) if index == opening_prologue_index else Vector2(12, 4) tick.size_flags_vertical = Control.SIZE_SHRINK_CENTER tick.color = ( Color(UI_PARCHMENT_TEXT.r, UI_PARCHMENT_TEXT.g, UI_PARCHMENT_TEXT.b, 0.92) @@ -3931,7 +3931,7 @@ func _create_hud() -> void: opening_title_column.add_child(opening_prologue_step_label) opening_prologue_body_label = Label.new() - opening_prologue_body_label.custom_minimum_size = Vector2(732, 126) + opening_prologue_body_label.custom_minimum_size = Vector2(730, 126) opening_prologue_body_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL opening_prologue_body_label.size_flags_vertical = Control.SIZE_EXPAND_FILL opening_prologue_body_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART @@ -3941,14 +3941,14 @@ func _create_hud() -> void: opening_caption_row.add_child(opening_prologue_body_label) var opening_button_column := VBoxContainer.new() - opening_button_column.custom_minimum_size = Vector2(170, 126) - opening_button_column.add_theme_constant_override("separation", 8) + opening_button_column.custom_minimum_size = Vector2(184, 126) + opening_button_column.add_theme_constant_override("separation", 9) opening_caption_row.add_child(opening_button_column) opening_prologue_skip_button = Button.new() - opening_prologue_skip_button.text = "전기 접기" + opening_prologue_skip_button.text = "서문 접기" opening_prologue_skip_button.icon = _make_toolbar_icon("campaign") - opening_prologue_skip_button.custom_minimum_size = Vector2(170, 42) + opening_prologue_skip_button.custom_minimum_size = Vector2(184, 44) opening_prologue_skip_button.tooltip_text = "서문을 접고 군막으로 이동합니다." opening_prologue_skip_button.expand_icon = false opening_prologue_skip_button.add_theme_constant_override("icon_max_width", 22) @@ -3961,9 +3961,9 @@ func _create_hud() -> void: opening_prologue_skip_button.pressed.connect(_on_opening_prologue_skip_pressed) opening_prologue_next_button = Button.new() - opening_prologue_next_button.text = "전기 넘기기" + opening_prologue_next_button.text = "다음 기록" opening_prologue_next_button.icon = _make_toolbar_icon("move") - opening_prologue_next_button.custom_minimum_size = Vector2(170, 48) + opening_prologue_next_button.custom_minimum_size = Vector2(184, 50) opening_prologue_next_button.tooltip_text = "다음 기록을 펼칩니다." opening_prologue_next_button.expand_icon = false opening_prologue_next_button.add_theme_constant_override("icon_max_width", 22) @@ -14444,7 +14444,7 @@ func _update_opening_prologue_page() -> void: _apply_opening_caption_text_style(opening_prologue_step_label, UI_OPENING_CAPTION_MUTED) if opening_prologue_next_button != null: var is_last_page := opening_prologue_index >= page_count - 1 - opening_prologue_next_button.text = "군막으로" if is_last_page else "전기 넘기기" + opening_prologue_next_button.text = "군막으로" if is_last_page else "다음 기록" opening_prologue_next_button.icon = _make_toolbar_icon("campaign" if is_last_page else "move") opening_prologue_next_button.tooltip_text = "군막으로 이동합니다." if is_last_page else "다음 기록을 펼칩니다." _rebuild_opening_story_beat_strip() diff --git a/tools/smoke_title_menu.gd b/tools/smoke_title_menu.gd index 4783730..00a1e1f 100644 --- a/tools/smoke_title_menu.gd +++ b/tools/smoke_title_menu.gd @@ -272,10 +272,10 @@ func _check_title_menu_start_load_and_settings(failures: Array[String]) -> void: if scene.opening_prologue_root != null: opening_story_strip = scene.opening_prologue_root.find_child("OpeningStoryLedgerStrip", true, false) if opening_story_strip == null: - failures.append("opening prologue should show a compact story ledger in the upper-right corner") + failures.append("opening prologue should show a compact story ledger beside the chapter seal") else: var progress_caption := opening_story_strip.find_child("OpeningStoryProgressCaption", true, false) as Label - if progress_caption == null or progress_caption.text != "영천으로": + if progress_caption == null or progress_caption.text != "제1장 영천으로": failures.append("opening story ledger should use a natural Korean chapter caption") var progress_scene := opening_story_strip.find_child("OpeningStoryProgressScene", true, false) as Label if progress_scene == null or not progress_scene.text.contains("첫 기록") or not progress_scene.text.contains("황건의 난"): @@ -301,14 +301,17 @@ func _check_title_menu_start_load_and_settings(failures: Array[String]) -> void: for numeric_label in numeric_labels: if str((numeric_label as Label).text).strip_edges() in ["1", "2", "3", "4", "1/4", "2/4", "3/4", "4/4"]: failures.append("opening story ledger should not expose bare numeric labels") - if opening_story_strip.custom_minimum_size.x > 380.0 or opening_story_strip.custom_minimum_size.y > 62.0: - failures.append("opening story ledger should stay compact in the upper-right corner: %s" % str(opening_story_strip.custom_minimum_size)) + var ledger_control := opening_story_strip as Control + if ledger_control != null and (ledger_control.position.x < 300.0 or ledger_control.position.x > 680.0): + failures.append("opening story ledger should sit beside the chapter seal instead of the upper-right corner: %s" % str(ledger_control.position)) + if opening_story_strip.custom_minimum_size.x > 340.0 or opening_story_strip.custom_minimum_size.y > 54.0: + failures.append("opening story ledger should stay compact beside the chapter seal: %s" % str(opening_story_strip.custom_minimum_size)) _check_control_inside_design_view(failures, opening_story_strip as Control, "opening story ledger") - if scene.opening_prologue_next_button == null or scene.opening_prologue_next_button.text != "전기 넘기기": + if scene.opening_prologue_next_button == null or scene.opening_prologue_next_button.text != "다음 기록": failures.append("opening prologue next button should read as a story action") else: _check_opening_story_button_art(failures, scene.opening_prologue_next_button, "opening next") - if scene.opening_prologue_skip_button == null or scene.opening_prologue_skip_button.text != "전기 접기": + if scene.opening_prologue_skip_button == null or scene.opening_prologue_skip_button.text != "서문 접기": failures.append("opening prologue skip button should read as a contextual camp shortcut") else: _check_opening_story_button_art(failures, scene.opening_prologue_skip_button, "opening skip") @@ -555,6 +558,9 @@ func _check_opening_story_button_art(failures: Array[String], button: Button, co if not (stylebox is StyleBoxTexture): failures.append("%s should keep a generated button texture" % context) return + var style := stylebox as StyleBoxTexture + if style.texture_margin_left < 24 or style.texture_margin_top < 10: + failures.append("%s generated button slices should preserve the ornamental frame at QHD: %d/%d" % [context, style.texture_margin_left, style.texture_margin_top]) _check_button_texture_slice_fit(failures, button, context) if button.get_theme_font_size("font_size") > 17: failures.append("%s should stay visually quiet in the story caption" % context) diff --git a/tools/smoke_visual_assets.gd b/tools/smoke_visual_assets.gd index 854867c..ccc2ef3 100644 --- a/tools/smoke_visual_assets.gd +++ b/tools/smoke_visual_assets.gd @@ -3961,7 +3961,7 @@ func _check_opening_prologue_presentation(failures: Array[String]) -> void: if scene.opening_prologue_next_button == null: failures.append("opening prologue should expose a next button") else: - if scene.opening_prologue_next_button.text != "전기 넘기기": + if scene.opening_prologue_next_button.text != "다음 기록": failures.append("opening next button should use story-fluent Korean text") if scene.opening_prologue_next_button.icon == null: failures.append("opening next button should carry generated icon art") @@ -3971,7 +3971,7 @@ func _check_opening_prologue_presentation(failures: Array[String]) -> void: if scene.opening_prologue_skip_button == null: failures.append("opening prologue should expose a skip button") else: - if scene.opening_prologue_skip_button.text != "전기 접기": + if scene.opening_prologue_skip_button.text != "서문 접기": failures.append("opening skip button should avoid modern skip-button wording") if scene.opening_prologue_skip_button.icon == null: failures.append("opening skip button should carry generated icon art") @@ -3982,8 +3982,13 @@ func _check_opening_prologue_presentation(failures: Array[String]) -> void: if opening_ledger == null: failures.append("opening prologue should use a story ledger instead of numbered progress cards") else: - if opening_ledger.custom_minimum_size.x > 380.0 or opening_ledger.custom_minimum_size.y > 62.0: + if opening_ledger.position.x < 300.0 or opening_ledger.position.x > 680.0: + failures.append("opening story ledger should sit beside the chapter seal instead of the upper-right corner: %s" % str(opening_ledger.position)) + if opening_ledger.custom_minimum_size.x > 340.0 or opening_ledger.custom_minimum_size.y > 54.0: failures.append("opening story ledger should stay compact at QHD scale: %s" % str(opening_ledger.custom_minimum_size)) + var progress_caption := opening_ledger.find_child("OpeningStoryProgressCaption", true, false) as Label + if progress_caption == null or progress_caption.text != "제1장 영천으로": + failures.append("opening story ledger should read as a chapter record, got %s" % ("" if progress_caption == null else progress_caption.text)) var old_cards := opening_ledger.find_children("OpeningStoryBeatCard*", "", true, false) if not old_cards.is_empty(): failures.append("opening story ledger should not expose old numbered-card style nodes") @@ -4424,7 +4429,7 @@ func _check_button_uses_generated_surface(failures: Array[String], button: Butto failures.append("%s text button should keep the wide generated button surface" % context) if style.texture.get_width() < 512 or style.texture.get_height() < 192: failures.append("%s generated button texture should keep high-resolution source pixels: %dx%d" % [context, style.texture.get_width(), style.texture.get_height()]) - var min_top_margin := 8 if bool(button.get_meta("opening_story_button", false)) else 10 + var min_top_margin := 10 if bool(button.get_meta("opening_story_button", false)) else 10 _check_button_texture_slice_for_control(failures, button, style, context, 16, min_top_margin) var min_content_margin := 6 if bool(button.get_meta("opening_story_button", false)) else 8 if style.content_margin_left < min_content_margin or style.content_margin_top < 5: