diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 614ee5e..4af9172 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -36,7 +36,7 @@ 1. Load battle data. 2. Hydrate deployments through `DataCatalog`. 3. Dispatch battle-start scenario events. -4. New campaigns first show a short title-to-briefing prologue using full-screen generated story artwork with a bottom scroll caption panel, then continue into the first scenario briefing. +4. New campaigns first show a short title-to-briefing prologue using full-screen generated story artwork with a bottom scroll caption panel and framed generated story-beat seals, then continue into the first scenario briefing. 5. Show scenario briefing with the campaign chapter header, battle header, objective summary, chapter overview, selectable camp Talk conversations, manual checkpoint menu, and optional pre-battle shop, Armory, Roster, and Formation setup. 6. Player selects a unit. 7. Briefing completion dispatches battle-begin events, including opening dialogue. diff --git a/scripts/scenes/battle_scene.gd b/scripts/scenes/battle_scene.gd index 504e12e..d1ead6a 100644 --- a/scripts/scenes/battle_scene.gd +++ b/scripts/scenes/battle_scene.gd @@ -146,12 +146,15 @@ 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(314, 96) -const TITLE_STORY_PREVIEW_STRIP_SIZE := Vector2(616, 86) -const TITLE_STORY_PREVIEW_CARD_SIZE := Vector2(140, 66) -const TITLE_STORY_PREVIEW_IMAGE_SIZE := Vector2(132, 44) +const TITLE_STORY_PREVIEW_STRIP_SIZE := Vector2(616, 92) +const TITLE_STORY_PREVIEW_CARD_SIZE := Vector2(140, 72) +const TITLE_STORY_PREVIEW_IMAGE_FRAME_SIZE := Vector2(132, 50) +const TITLE_STORY_PREVIEW_IMAGE_SIZE := Vector2(124, 42) const TITLE_STORY_PREVIEW_LABEL_SIZE := Vector2(132, 15) -const OPENING_STORY_BEAT_CARD_SIZE := Vector2(122, 66) -const OPENING_STORY_BEAT_IMAGE_SIZE := Vector2(112, 42) +const OPENING_STORY_BEAT_CARD_SIZE := Vector2(136, 78) +const OPENING_STORY_BEAT_IMAGE_FRAME_SIZE := Vector2(126, 48) +const OPENING_STORY_BEAT_IMAGE_SIZE := Vector2(118, 40) +const OPENING_STORY_BEAT_LABEL_SIZE := Vector2(104, 18) const COMMAND_NOTICE_ICON_SIZE := Vector2(32, 32) const COMMAND_NOTICE_MAX_ICONS := 4 const CHAPTER_SEAL_BADGE_SIZE := Vector2(54, 54) @@ -2082,7 +2085,7 @@ func _make_title_story_preview_strip() -> PanelContainer: _apply_panel_style(panel, "hud_info") var row := HBoxContainer.new() - row.custom_minimum_size = Vector2(584, 66) + row.custom_minimum_size = Vector2(584, 72) row.add_theme_constant_override("separation", 8) panel.add_child(row) @@ -2106,11 +2109,24 @@ func _make_title_story_preview_card(title: String, image_path: String, index: in card_style.content_margin_bottom = 3 var column := VBoxContainer.new() - column.custom_minimum_size = Vector2(132, 59) + column.custom_minimum_size = Vector2(132, 65) column.add_theme_constant_override("separation", 2) column.tooltip_text = card.tooltip_text card.add_child(column) + var image_frame := PanelContainer.new() + image_frame.name = "TitleStoryPreviewImageFrame" + image_frame.custom_minimum_size = TITLE_STORY_PREVIEW_IMAGE_FRAME_SIZE + image_frame.tooltip_text = card.tooltip_text + _apply_panel_style(image_frame, "caption") + var frame_style := image_frame.get_theme_stylebox("panel") + if frame_style != null: + frame_style.content_margin_left = 3 + frame_style.content_margin_right = 3 + frame_style.content_margin_top = 3 + frame_style.content_margin_bottom = 3 + column.add_child(image_frame) + var texture_rect := TextureRect.new() texture_rect.name = "TitleStoryPreviewImage" texture_rect.custom_minimum_size = TITLE_STORY_PREVIEW_IMAGE_SIZE @@ -2119,7 +2135,7 @@ func _make_title_story_preview_card(title: String, image_path: String, index: in 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 - column.add_child(texture_rect) + image_frame.add_child(texture_rect) var label := Label.new() label.name = "TitleStoryPreviewLabel" @@ -2136,14 +2152,14 @@ 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 = "OpeningStoryBeatStrip" - panel.position = Vector2(630, 32) - panel.size = Vector2(594, 86) - panel.custom_minimum_size = Vector2(594, 86) + panel.position = Vector2(604, 28) + panel.size = Vector2(620, 98) + panel.custom_minimum_size = Vector2(620, 98) _apply_panel_style(panel, "hud_info") opening_prologue_story_strip = HBoxContainer.new() opening_prologue_story_strip.name = "OpeningStoryBeatRow" - opening_prologue_story_strip.custom_minimum_size = Vector2(562, 66) + opening_prologue_story_strip.custom_minimum_size = Vector2(588, 78) opening_prologue_story_strip.add_theme_constant_override("separation", 8) panel.add_child(opening_prologue_story_strip) return panel @@ -2179,11 +2195,46 @@ func _make_opening_story_beat_card(page: Dictionary, index: int, is_current: boo card.modulate = Color(1.0, 1.0, 1.0, 1.0) if is_current else Color(0.56, 0.55, 0.50, 0.88) var column := VBoxContainer.new() - column.custom_minimum_size = Vector2(112, 59) - column.add_theme_constant_override("separation", 2) + column.custom_minimum_size = Vector2(126, 69) + column.add_theme_constant_override("separation", 3) column.tooltip_text = tooltip card.add_child(column) + var title_row := HBoxContainer.new() + title_row.custom_minimum_size = Vector2(126, 18) + title_row.add_theme_constant_override("separation", 4) + title_row.tooltip_text = tooltip + column.add_child(title_row) + + var seal := _make_seal_tile("%d" % (index + 1), 18) + seal.name = "OpeningStoryBeatSeal" + seal.modulate = Color(1.0, 1.0, 1.0, 1.0) if is_current else Color(0.70, 0.68, 0.62, 0.90) + title_row.add_child(seal) + + var label := Label.new() + label.name = "OpeningStoryBeatLabel" + label.text = title + label.custom_minimum_size = OPENING_STORY_BEAT_LABEL_SIZE + label.horizontal_alignment = HORIZONTAL_ALIGNMENT_LEFT + label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER + label.clip_text = true + label.tooltip_text = tooltip + _apply_label_style(label, UI_PARCHMENT_TEXT if is_current else UI_OLD_BRONZE, 9) + title_row.add_child(label) + + var image_frame := PanelContainer.new() + image_frame.name = "OpeningStoryBeatImageFrame" + image_frame.custom_minimum_size = OPENING_STORY_BEAT_IMAGE_FRAME_SIZE + image_frame.tooltip_text = tooltip + _apply_panel_style(image_frame, "caption") + var image_frame_style := image_frame.get_theme_stylebox("panel") + if image_frame_style != null: + image_frame_style.content_margin_left = 3 + image_frame_style.content_margin_right = 3 + image_frame_style.content_margin_top = 3 + image_frame_style.content_margin_bottom = 3 + column.add_child(image_frame) + var image := TextureRect.new() image.name = "OpeningStoryBeatImage" image.custom_minimum_size = OPENING_STORY_BEAT_IMAGE_SIZE @@ -2192,18 +2243,7 @@ func _make_opening_story_beat_card(page: Dictionary, index: int, is_current: boo image.texture = _load_art_texture(image_path) image.modulate = Color(1.0, 0.96, 0.88, 0.98) if is_current else Color(0.82, 0.80, 0.74, 0.72) image.tooltip_text = tooltip - column.add_child(image) - - var label := Label.new() - label.name = "OpeningStoryBeatLabel" - label.text = "%d %s" % [index + 1, title] - label.custom_minimum_size = Vector2(112, 15) - label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER - label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER - label.clip_text = true - label.tooltip_text = tooltip - _apply_label_style(label, UI_PARCHMENT_TEXT if is_current else UI_OLD_BRONZE, 9) - column.add_child(label) + image_frame.add_child(image) _set_control_tree_tooltip(card, tooltip) return card diff --git a/tools/smoke_title_menu.gd b/tools/smoke_title_menu.gd index aa2b501..146450d 100644 --- a/tools/smoke_title_menu.gd +++ b/tools/smoke_title_menu.gd @@ -91,6 +91,9 @@ func _check_title_menu_start_load_and_settings(failures: Array[String]) -> void: 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 feature_frame := title_story_feature.find_child("TitleStoryFeatureImageFrame", true, false) as PanelContainer + if feature_frame == null or not bool(feature_frame.get_meta("generated_panel_texture", false)): + failures.append("title story feature artwork should sit inside a generated frame") var title_story_strip: Node = null if scene.title_screen_root != null: title_story_strip = scene.title_screen_root.find_child("TitleStoryPreviewStrip", true, false) @@ -103,8 +106,13 @@ func _check_title_menu_start_load_and_settings(failures: Array[String]) -> void: for story_card in story_cards: if not story_card is PanelContainer: failures.append("title story preview cards should use generated panel frames") + elif not bool((story_card as PanelContainer).get_meta("generated_panel_texture", false)): + failures.append("title story preview card should use a generated panel texture") + var story_frame := (story_card as Node).find_child("TitleStoryPreviewImageFrame", true, false) as PanelContainer 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_frame == null or not bool(story_frame.get_meta("generated_panel_texture", false)): + failures.append("title story preview image should sit inside a generated frame") if story_image == null or story_image.texture == null: failures.append("title story preview card should load generated artwork") elif story_image.custom_minimum_size.y < 42.0: @@ -200,10 +208,18 @@ func _check_title_menu_start_load_and_settings(failures: Array[String]) -> void: var current_index := 0 for opening_story_card in opening_story_cards: var card_node := opening_story_card as Node + var beat_frame := card_node.find_child("OpeningStoryBeatImageFrame", true, false) as PanelContainer + var beat_seal := card_node.find_child("OpeningStoryBeatSeal", true, false) as PanelContainer var beat_image := card_node.find_child("OpeningStoryBeatImage", true, false) as TextureRect var beat_label := card_node.find_child("OpeningStoryBeatLabel", true, false) as Label + if beat_frame == null or not bool(beat_frame.get_meta("generated_panel_texture", false)): + failures.append("opening story beat image should sit inside a generated frame") + if beat_seal == null or not bool(beat_seal.get_meta("generated_panel_texture", false)): + failures.append("opening story beat should use a generated seal number") if beat_image == null or beat_image.texture == null: failures.append("opening story beat card should load generated artwork") + elif beat_image.custom_minimum_size.x < 116.0: + failures.append("opening story beat artwork should stay readable in the strip: %s" % str(beat_image.custom_minimum_size)) if beat_label == null or beat_label.text.strip_edges().is_empty(): failures.append("opening story beat card should name its scene") if bool(card_node.get_meta("current_story_beat", false)):