diff --git a/README.md b/README.md index 9891606..afc0abb 100644 --- a/README.md +++ b/README.md @@ -126,7 +126,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 and Cao Cao portrait art, offers start/load/settings, leads new campaigns through 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, and a bottom scroll caption panel, exposes the same audio/edge-scroll settings from the top in-game toolbar, includes a one-click audio recovery control, and persists BGM/SFX volume, sound toggles, and edge-scroll preference in `user://heros_settings.json`. +- Cinematic title screen uses a dedicated ancient-war-camp dawn backdrop and Cao Cao portrait art, offers start/load/settings, previews the four generated opening story panels on the title menu, leads new campaigns through 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, and a bottom scroll caption panel, exposes the same audio/edge-scroll settings from the top in-game toolbar, includes a one-click audio recovery control, and persists BGM/SFX volume, sound toggles, and edge-scroll preference 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 92bf403..f65f6a6 100644 --- a/scripts/scenes/battle_scene.gd +++ b/scripts/scenes/battle_scene.gd @@ -1954,6 +1954,52 @@ func _make_seal_cluster(height: float) -> HBoxContainer: return cluster +func _make_title_story_preview_strip() -> PanelContainer: + var panel := PanelContainer.new() + panel.name = "TitleStoryPreviewStrip" + panel.custom_minimum_size = Vector2(616, 88) + _apply_panel_style(panel, "hud_info") + + var row := HBoxContainer.new() + row.custom_minimum_size = Vector2(584, 68) + row.add_theme_constant_override("separation", 8) + panel.add_child(row) + + for index in range(OPENING_PROLOGUE_PAGES.size()): + var page: Dictionary = OPENING_PROLOGUE_PAGES[index] + row.add_child(_make_title_story_preview_card(str(page.get("title", "")), str(page.get("image", "")), index + 1)) + return panel + + +func _make_title_story_preview_card(title: String, image_path: String, index: int) -> VBoxContainer: + var card := VBoxContainer.new() + card.name = "TitleStoryPreviewCard%d" % index + card.custom_minimum_size = Vector2(140, 68) + card.add_theme_constant_override("separation", 3) + card.tooltip_text = "%d. %s" % [index, title] + + var texture_rect := TextureRect.new() + texture_rect.name = "TitleStoryPreviewImage" + texture_rect.custom_minimum_size = Vector2(140, 48) + texture_rect.expand_mode = TextureRect.EXPAND_IGNORE_SIZE + texture_rect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_COVERED + texture_rect.texture = _load_art_texture(image_path) + texture_rect.modulate = Color(0.96, 0.94, 0.88, 0.94) + texture_rect.tooltip_text = card.tooltip_text + card.add_child(texture_rect) + + var label := Label.new() + label.name = "TitleStoryPreviewLabel" + label.text = title + label.custom_minimum_size = Vector2(140, 17) + label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER + label.tooltip_text = card.tooltip_text + _apply_label_style(label, UI_OLD_BRONZE, 10) + card.add_child(label) + return card + + func _make_map_lattice_overlay(size: Vector2) -> Control: var overlay := Control.new() overlay.name = "MapLatticeOverlay" @@ -3605,9 +3651,9 @@ func _create_hud() -> void: title_panel = PanelContainer.new() title_panel.name = "TitleMenuPanel" - title_panel.position = Vector2(58, 76) - title_panel.size = Vector2(708, 568) - title_panel.custom_minimum_size = Vector2(708, 568) + title_panel.position = Vector2(58, 46) + title_panel.size = Vector2(708, 628) + title_panel.custom_minimum_size = Vector2(708, 628) _apply_panel_style(title_panel, "paper") title_screen_root.add_child(title_panel) @@ -3634,6 +3680,8 @@ func _create_hud() -> void: _apply_label_style(title_subtitle_label, UI_OLD_BRONZE, 17) title_column.add_child(title_subtitle_label) + title_column.add_child(_make_title_story_preview_strip()) + var title_body_panel := PanelContainer.new() title_body_panel.custom_minimum_size = Vector2(616, 314) _apply_panel_style(title_body_panel, "edict") diff --git a/tools/smoke_title_menu.gd b/tools/smoke_title_menu.gd index 01e1928..a36ff72 100644 --- a/tools/smoke_title_menu.gd +++ b/tools/smoke_title_menu.gd @@ -74,6 +74,22 @@ func _check_title_menu_start_load_and_settings(failures: Array[String]) -> void: failures.append("title menu panel should sit on the left side of the cinematic title screen") if scene.title_caption_label == null or scene.title_caption_label.text != "조조전": failures.append("title screen should present the campaign title in Korean") + var title_story_strip: Node = null + if scene.title_screen_root != null: + title_story_strip = scene.title_screen_root.find_child("TitleStoryPreviewStrip", true, false) + if title_story_strip == null: + failures.append("title screen should preview the four generated opening story panels") + else: + var story_cards: Array = title_story_strip.find_children("TitleStoryPreviewCard*", "", true, false) + if story_cards.size() != 4: + failures.append("title story preview should expose four story beats: %d" % story_cards.size()) + for story_card in story_cards: + var story_image := (story_card as Node).find_child("TitleStoryPreviewImage", true, false) as TextureRect + var story_label := (story_card as Node).find_child("TitleStoryPreviewLabel", true, false) as Label + if story_image == null or story_image.texture == null: + failures.append("title story preview card should load generated artwork") + if story_label == null or story_label.text.strip_edges().is_empty(): + failures.append("title story preview card should name its story beat") if scene.briefing_panel == null or scene.briefing_panel.visible: failures.append("briefing should stay hidden behind the title screen") if scene.title_start_button == null or scene.title_start_button.text != "처음부터 시작":