diff --git a/README.md b/README.md index 022d370..1342342 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, 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, a highlighted four-beat story strip, 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, opens with a large generated Yellow Turban story feature panel plus four generated opening story previews 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, a highlighted four-beat story strip, 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 ed54881..f5d9cea 100644 --- a/scripts/scenes/battle_scene.gd +++ b/scripts/scenes/battle_scene.gd @@ -142,6 +142,8 @@ const BRIEFING_TACTICAL_MARKER_MAX_COUNT := 3 const BRIEFING_FORCE_PREVIEW_ICON_SIZE := Vector2(32, 32) const BRIEFING_FORCE_PREVIEW_MAX_ALLIES := 2 const BRIEFING_FORCE_PREVIEW_MAX_ENEMY_TYPES := 4 +const TITLE_STORY_FEATURE_SIZE := Vector2(616, 122) +const TITLE_STORY_FEATURE_IMAGE_SIZE := Vector2(238, 96) const OPENING_STORY_BEAT_CARD_SIZE := Vector2(122, 66) const OPENING_STORY_BEAT_IMAGE_SIZE := Vector2(112, 42) const COMMAND_NOTICE_ICON_SIZE := Vector2(32, 32) @@ -1968,14 +1970,85 @@ func _make_seal_cluster(height: float) -> HBoxContainer: return cluster -func _make_title_story_preview_strip() -> PanelContainer: +func _make_title_story_feature_panel() -> PanelContainer: + var page: Dictionary = OPENING_PROLOGUE_PAGES[0] + var title := str(page.get("title", "황건의 난")) + var body := str(page.get("body", "")) + var image_path := str(page.get("image", "")) + var tooltip := "%s\n%s" % [title, body] var panel := PanelContainer.new() - panel.name = "TitleStoryPreviewStrip" - panel.custom_minimum_size = Vector2(616, 88) + panel.name = "TitleStoryFeaturePanel" + panel.custom_minimum_size = TITLE_STORY_FEATURE_SIZE + panel.tooltip_text = tooltip _apply_panel_style(panel, "hud_info") var row := HBoxContainer.new() - row.custom_minimum_size = Vector2(584, 68) + row.custom_minimum_size = Vector2(584, 104) + row.add_theme_constant_override("separation", 10) + row.tooltip_text = tooltip + panel.add_child(row) + + var image_stack := PanelContainer.new() + image_stack.name = "TitleStoryFeatureImageFrame" + image_stack.custom_minimum_size = Vector2(252, 104) + image_stack.tooltip_text = tooltip + _apply_panel_style(image_stack, "caption") + row.add_child(image_stack) + + var image := TextureRect.new() + image.name = "TitleStoryFeatureImage" + image.custom_minimum_size = TITLE_STORY_FEATURE_IMAGE_SIZE + image.expand_mode = TextureRect.EXPAND_IGNORE_SIZE + image.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_COVERED + image.texture = _load_art_texture(image_path) + image.modulate = Color(0.98, 0.95, 0.88, 0.98) + image.tooltip_text = tooltip + image_stack.add_child(image) + + var text_column := VBoxContainer.new() + text_column.custom_minimum_size = Vector2(320, 104) + text_column.size_flags_horizontal = Control.SIZE_EXPAND_FILL + text_column.add_theme_constant_override("separation", 4) + text_column.tooltip_text = tooltip + row.add_child(text_column) + + var title_row := HBoxContainer.new() + title_row.custom_minimum_size = Vector2(320, 26) + title_row.add_theme_constant_override("separation", 6) + title_row.tooltip_text = tooltip + text_column.add_child(title_row) + title_row.add_child(_make_seal_tile("1", 24)) + + var title_label := Label.new() + title_label.name = "TitleStoryFeatureTitle" + title_label.text = title + title_label.custom_minimum_size = Vector2(284, 26) + title_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER + title_label.tooltip_text = tooltip + _apply_label_style(title_label, UI_PARCHMENT_TEXT, 18) + title_row.add_child(title_label) + + var body_label := Label.new() + body_label.name = "TitleStoryFeatureBody" + body_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + body_label.custom_minimum_size = Vector2(320, 68) + body_label.tooltip_text = tooltip + _apply_label_style(body_label, UI_AGED_INK, 13) + _set_fitted_label_text(body_label, body, 13, 10, body_label.custom_minimum_size) + text_column.add_child(body_label) + + _set_control_tree_tooltip(panel, tooltip) + return panel + + +func _make_title_story_preview_strip() -> PanelContainer: + var panel := PanelContainer.new() + panel.name = "TitleStoryPreviewStrip" + panel.custom_minimum_size = Vector2(616, 78) + _apply_panel_style(panel, "hud_info") + + var row := HBoxContainer.new() + row.custom_minimum_size = Vector2(584, 58) row.add_theme_constant_override("separation", 8) panel.add_child(row) @@ -1988,13 +2061,13 @@ func _make_title_story_preview_strip() -> PanelContainer: 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.custom_minimum_size = Vector2(140, 58) 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.custom_minimum_size = Vector2(140, 38) 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) @@ -3764,22 +3837,22 @@ func _create_hud() -> void: title_panel = PanelContainer.new() title_panel.name = "TitleMenuPanel" - title_panel.position = Vector2(58, 46) - title_panel.size = Vector2(708, 628) - title_panel.custom_minimum_size = Vector2(708, 628) + title_panel.position = Vector2(58, 24) + title_panel.size = Vector2(708, 670) + title_panel.custom_minimum_size = Vector2(708, 670) _apply_panel_style(title_panel, "paper") title_screen_root.add_child(title_panel) var title_column := VBoxContainer.new() title_column.alignment = BoxContainer.ALIGNMENT_CENTER - title_column.add_theme_constant_override("separation", 10) + title_column.add_theme_constant_override("separation", 7) title_panel.add_child(title_column) title_column.add_child(_make_scroll_rod(616, 5)) title_column.add_child(_make_seal_ribbon(616, 18)) title_caption_label = Label.new() title_caption_label.text = "조조전" - title_caption_label.custom_minimum_size = Vector2(616, 58) + title_caption_label.custom_minimum_size = Vector2(616, 48) title_caption_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER title_caption_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER _apply_label_style(title_caption_label, UI_PARCHMENT_TEXT, 34) @@ -3787,16 +3860,17 @@ func _create_hud() -> void: title_subtitle_label = Label.new() title_subtitle_label.text = "난세를 여는 첫 군령" - title_subtitle_label.custom_minimum_size = Vector2(616, 34) + title_subtitle_label.custom_minimum_size = Vector2(616, 26) title_subtitle_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER title_subtitle_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER _apply_label_style(title_subtitle_label, UI_OLD_BRONZE, 17) title_column.add_child(title_subtitle_label) + title_column.add_child(_make_title_story_feature_panel()) title_column.add_child(_make_title_story_preview_strip()) var title_body_panel := PanelContainer.new() - title_body_panel.custom_minimum_size = Vector2(616, 314) + title_body_panel.custom_minimum_size = Vector2(616, 300) _apply_panel_style(title_body_panel, "edict") title_column.add_child(title_body_panel) diff --git a/tools/smoke_title_menu.gd b/tools/smoke_title_menu.gd index dbfb596..4f8cf04 100644 --- a/tools/smoke_title_menu.gd +++ b/tools/smoke_title_menu.gd @@ -74,6 +74,21 @@ 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_feature: Node = null + if scene.title_screen_root != null: + title_story_feature = scene.title_screen_root.find_child("TitleStoryFeaturePanel", true, false) + if title_story_feature == null: + failures.append("title screen should feature a large generated opening story panel") + else: + var feature_image := title_story_feature.find_child("TitleStoryFeatureImage", true, false) as TextureRect + var feature_title := title_story_feature.find_child("TitleStoryFeatureTitle", true, false) as Label + var feature_body := title_story_feature.find_child("TitleStoryFeatureBody", true, false) as Label + if feature_image == null or feature_image.texture == null: + failures.append("title story feature should load generated opening artwork") + if feature_title == null or feature_title.text != "황건의 난": + failures.append("title story feature should name the first opening beat") + if feature_body == null or not feature_body.text.contains("조조") or not feature_body.text.contains("황건의 난"): + failures.append("title story feature should summarize the first opening beat 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)