From 641aeb6b3358823e347fc65fdf56f6ef256fd663 Mon Sep 17 00:00:00 2001 From: Wickedness Date: Sat, 20 Jun 2026 09:03:44 +0900 Subject: [PATCH] Make opening prologue more image led --- scripts/scenes/battle_scene.gd | 135 +++++++++++++++++---------------- tools/smoke_title_menu.gd | 27 +++++-- 2 files changed, 92 insertions(+), 70 deletions(-) diff --git a/scripts/scenes/battle_scene.gd b/scripts/scenes/battle_scene.gd index 64833cd..a2692cc 100644 --- a/scripts/scenes/battle_scene.gd +++ b/scripts/scenes/battle_scene.gd @@ -46,21 +46,25 @@ const SETTINGS_SAVE_VERSION := 1 const OPENING_PROLOGUE_PAGES := [ { "title": "황건의 난", + "teaser": "황건의 난으로 고을이 불타자, 조조는 난세의 첫 군령을 결심한다.", "body": "황건의 난이 일어나자 누런 머릿수건을 맨 무리가 고을을 불태우고 길을 끊었다. 젊은 조조는 무너지는 질서와 굶주린 백성을 보며 더는 관망할 수 없음을 깨달았다.", "image": OPENING_PROLOGUE_REBELLION_PATH }, { "title": "맹덕의 결심", + "teaser": "분노를 삼킨 조조는 작은 군세라도 직접 세워 길을 열기로 한다.", "body": "황건의 봉기가 고을을 삼키자 조조는 더는 조정의 명만 기다리지 않았다. 그는 작은 군세라도 직접 세워 백성이 빠져나갈 길을 열기로 했다.", "image": OPENING_PROLOGUE_RESOLVE_PATH }, { "title": "하후돈의 합류", + "teaser": "가문의 친척 하후돈이 창을 들고 찾아와 조조의 첫 군령에 선다.", "body": "가문의 친척 하후돈은 먼저 창을 들고 조조의 길에 섰다. 두 사람은 큰 명성보다 무너지는 고을을 구하는 첫 군령을 함께 택했다.", "image": OPENING_PROLOGUE_XIAHOU_DUN_PATH }, { "title": "영천으로", + "teaser": "영천 들판에서 조조와 하후돈은 황건적의 깃발을 마주한다.", "body": "영천 들판에서 조조와 하후돈은 황건적의 깃발을 마주했다. 마을과 성채를 기대어 버티는 적을 꺾고, 백성이 빠져나갈 길을 열어야 한다.", "image": OPENING_PROLOGUE_YINGCHUAN_PATH } @@ -147,17 +151,17 @@ 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(646, 180) -const TITLE_STORY_FEATURE_IMAGE_SIZE := Vector2(398, 150) -const TITLE_STORY_PREVIEW_STRIP_SIZE := Vector2(646, 108) -const TITLE_STORY_PREVIEW_CARD_SIZE := Vector2(148, 88) -const TITLE_STORY_PREVIEW_IMAGE_FRAME_SIZE := Vector2(140, 64) -const TITLE_STORY_PREVIEW_IMAGE_SIZE := Vector2(132, 56) -const TITLE_STORY_PREVIEW_LABEL_SIZE := Vector2(140, 18) -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 TITLE_STORY_FEATURE_SIZE := Vector2(646, 196) +const TITLE_STORY_FEATURE_IMAGE_SIZE := Vector2(442, 166) +const TITLE_STORY_PREVIEW_STRIP_SIZE := Vector2(646, 116) +const TITLE_STORY_PREVIEW_CARD_SIZE := Vector2(148, 98) +const TITLE_STORY_PREVIEW_IMAGE_FRAME_SIZE := Vector2(140, 74) +const TITLE_STORY_PREVIEW_IMAGE_SIZE := Vector2(132, 66) +const TITLE_STORY_PREVIEW_LABEL_SIZE := Vector2(140, 16) +const OPENING_STORY_BEAT_CARD_SIZE := Vector2(128, 68) +const OPENING_STORY_BEAT_IMAGE_FRAME_SIZE := Vector2(118, 42) +const OPENING_STORY_BEAT_IMAGE_SIZE := Vector2(116, 34) +const OPENING_STORY_BEAT_LABEL_SIZE := Vector2(96, 18) const COMMAND_NOTICE_ICON_SIZE := Vector2(32, 32) const COMMAND_NOTICE_MAX_ICONS := 4 const CHAPTER_SEAL_BADGE_SIZE := Vector2(54, 54) @@ -2301,24 +2305,24 @@ func _make_seal_cluster(height: float) -> HBoxContainer: 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 body := str(page.get("teaser", page.get("body", ""))) var image_path := str(page.get("image", "")) var tooltip := "%s\n%s" % [title, body] var panel := PanelContainer.new() panel.name = "TitleStoryFeaturePanel" panel.custom_minimum_size = TITLE_STORY_FEATURE_SIZE panel.tooltip_text = tooltip - _apply_panel_style(panel, "hud_info") + _apply_panel_style(panel, "transparent_overlay") var row := HBoxContainer.new() - row.custom_minimum_size = Vector2(616, 158) - row.add_theme_constant_override("separation", 12) + row.custom_minimum_size = Vector2(636, 176) + 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(410, 158) + image_stack.custom_minimum_size = Vector2(462, 176) image_stack.tooltip_text = tooltip _apply_panel_style(image_stack, "caption") row.add_child(image_stack) @@ -2334,14 +2338,14 @@ func _make_title_story_feature_panel() -> PanelContainer: image_stack.add_child(image) var text_column := VBoxContainer.new() - text_column.custom_minimum_size = Vector2(194, 158) + text_column.custom_minimum_size = Vector2(164, 176) text_column.size_flags_horizontal = Control.SIZE_EXPAND_FILL - text_column.add_theme_constant_override("separation", 6) + text_column.add_theme_constant_override("separation", 5) text_column.tooltip_text = tooltip row.add_child(text_column) var title_row := HBoxContainer.new() - title_row.custom_minimum_size = Vector2(194, 30) + title_row.custom_minimum_size = Vector2(164, 30) title_row.add_theme_constant_override("separation", 6) title_row.tooltip_text = tooltip text_column.add_child(title_row) @@ -2350,7 +2354,7 @@ func _make_title_story_feature_panel() -> PanelContainer: var title_label := Label.new() title_label.name = "TitleStoryFeatureTitle" title_label.text = title - title_label.custom_minimum_size = Vector2(158, 30) + title_label.custom_minimum_size = Vector2(130, 30) title_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER title_label.tooltip_text = tooltip _apply_label_style(title_label, UI_PARCHMENT_TEXT, 18) @@ -2359,10 +2363,10 @@ func _make_title_story_feature_panel() -> PanelContainer: var body_label := Label.new() body_label.name = "TitleStoryFeatureBody" body_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART - body_label.custom_minimum_size = Vector2(194, 116) + body_label.custom_minimum_size = Vector2(164, 122) 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) + _apply_label_style(body_label, UI_AGED_INK, 12) + _set_fitted_label_text(body_label, body, 12, 10, body_label.custom_minimum_size) text_column.add_child(body_label) _set_control_tree_tooltip(panel, tooltip) @@ -2373,10 +2377,10 @@ func _make_title_story_preview_strip() -> PanelContainer: var panel := PanelContainer.new() panel.name = "TitleStoryPreviewStrip" panel.custom_minimum_size = TITLE_STORY_PREVIEW_STRIP_SIZE - _apply_panel_style(panel, "hud_info") + _apply_panel_style(panel, "transparent_overlay") var row := HBoxContainer.new() - row.custom_minimum_size = Vector2(616, 88) + row.custom_minimum_size = Vector2(616, 98) row.add_theme_constant_override("separation", 8) panel.add_child(row) @@ -2400,7 +2404,7 @@ 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(140, 80) + column.custom_minimum_size = Vector2(140, 90) column.add_theme_constant_override("separation", 2) column.tooltip_text = card.tooltip_text card.add_child(column) @@ -2435,7 +2439,7 @@ func _make_title_story_preview_card(title: String, image_path: String, index: in 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) + _apply_label_style(label, UI_OLD_BRONZE, 9) column.add_child(label) return card @@ -2443,14 +2447,15 @@ 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(604, 28) - panel.size = Vector2(620, 98) - panel.custom_minimum_size = Vector2(620, 98) - _apply_panel_style(panel, "hud_info") + panel.position = Vector2(650, 24) + panel.size = Vector2(560, 82) + panel.custom_minimum_size = Vector2(560, 82) + panel.modulate = Color(1.0, 1.0, 1.0, 0.90) + _apply_panel_style(panel, "transparent_overlay") opening_prologue_story_strip = HBoxContainer.new() opening_prologue_story_strip.name = "OpeningStoryBeatRow" - opening_prologue_story_strip.custom_minimum_size = Vector2(588, 78) + opening_prologue_story_strip.custom_minimum_size = Vector2(536, 68) opening_prologue_story_strip.add_theme_constant_override("separation", 8) panel.add_child(opening_prologue_story_strip) return panel @@ -2486,13 +2491,13 @@ 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(126, 69) + column.custom_minimum_size = Vector2(118, 60) 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.custom_minimum_size = Vector2(118, 18) title_row.add_theme_constant_override("separation", 4) title_row.tooltip_text = tooltip column.add_child(title_row) @@ -3555,25 +3560,25 @@ func _create_hud() -> void: var opening_top_dim := ColorRect.new() opening_top_dim.name = "OpeningTopInkShade" - opening_top_dim.color = Color(0.020, 0.017, 0.014, 0.18) + opening_top_dim.color = Color(0.020, 0.017, 0.014, 0.13) opening_top_dim.mouse_filter = Control.MOUSE_FILTER_IGNORE opening_top_dim.position = Vector2(0, 0) - opening_top_dim.size = Vector2(1280, 122) + opening_top_dim.size = Vector2(1280, 92) opening_prologue_root.add_child(opening_top_dim) var opening_bottom_dim := ColorRect.new() opening_bottom_dim.name = "OpeningBottomInkShade" - opening_bottom_dim.color = Color(0.020, 0.018, 0.015, 0.50) + opening_bottom_dim.color = Color(0.020, 0.018, 0.015, 0.40) opening_bottom_dim.mouse_filter = Control.MOUSE_FILTER_IGNORE - opening_bottom_dim.position = Vector2(0, 488) - opening_bottom_dim.size = Vector2(1280, 232) + opening_bottom_dim.position = Vector2(0, 532) + opening_bottom_dim.size = Vector2(1280, 188) opening_prologue_root.add_child(opening_bottom_dim) var opening_chapter_panel := PanelContainer.new() opening_chapter_panel.name = "OpeningChapterSeal" - opening_chapter_panel.position = Vector2(56, 36) - opening_chapter_panel.size = Vector2(278, 58) - opening_chapter_panel.custom_minimum_size = Vector2(278, 58) + opening_chapter_panel.position = Vector2(48, 24) + opening_chapter_panel.size = Vector2(260, 52) + opening_chapter_panel.custom_minimum_size = Vector2(260, 52) _apply_panel_style(opening_chapter_panel, "caption") opening_prologue_root.add_child(opening_chapter_panel) @@ -3581,68 +3586,68 @@ func _create_hud() -> void: opening_chapter_label.text = "제1장 영천으로" opening_chapter_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER opening_chapter_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER - _apply_label_style(opening_chapter_label, UI_PARCHMENT_TEXT, 21) + _apply_label_style(opening_chapter_label, UI_PARCHMENT_TEXT, 19) opening_chapter_panel.add_child(opening_chapter_label) opening_prologue_root.add_child(_make_opening_story_beat_strip()) var opening_caption_panel := PanelContainer.new() opening_caption_panel.name = "OpeningCaptionScroll" - opening_caption_panel.position = Vector2(64, 508) - opening_caption_panel.size = Vector2(1152, 148) - opening_caption_panel.custom_minimum_size = Vector2(1152, 148) + opening_caption_panel.position = Vector2(64, 548) + opening_caption_panel.size = Vector2(1152, 112) + opening_caption_panel.custom_minimum_size = Vector2(1152, 112) opening_caption_panel.modulate = Color(0.96, 0.95, 0.91, 0.99) _apply_panel_style(opening_caption_panel, "story_caption") opening_prologue_root.add_child(opening_caption_panel) var opening_caption_row := HBoxContainer.new() - opening_caption_row.custom_minimum_size = Vector2(1100, 102) - opening_caption_row.add_theme_constant_override("separation", 18) + opening_caption_row.custom_minimum_size = Vector2(1100, 72) + opening_caption_row.add_theme_constant_override("separation", 16) opening_caption_panel.add_child(opening_caption_row) var opening_title_column := VBoxContainer.new() - opening_title_column.custom_minimum_size = Vector2(226, 102) + opening_title_column.custom_minimum_size = Vector2(214, 72) opening_title_column.add_theme_constant_override("separation", 4) opening_caption_row.add_child(opening_title_column) - opening_title_column.add_child(_make_seal_ribbon(226, 12)) + opening_title_column.add_child(_make_seal_ribbon(214, 10)) opening_prologue_title_label = Label.new() - opening_prologue_title_label.custom_minimum_size = Vector2(226, 48) + opening_prologue_title_label.custom_minimum_size = Vector2(214, 32) opening_prologue_title_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER opening_prologue_title_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER - _apply_label_style(opening_prologue_title_label, UI_AGED_INK, 25) + _apply_label_style(opening_prologue_title_label, UI_AGED_INK, 22) opening_title_column.add_child(opening_prologue_title_label) opening_prologue_step_label = Label.new() - opening_prologue_step_label.custom_minimum_size = Vector2(226, 20) + opening_prologue_step_label.custom_minimum_size = Vector2(214, 18) opening_prologue_step_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER opening_prologue_step_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER _apply_label_style(opening_prologue_step_label, UI_TARNISHED_BRONZE, 14) opening_title_column.add_child(opening_prologue_step_label) opening_prologue_body_label = Label.new() - opening_prologue_body_label.custom_minimum_size = Vector2(664, 102) + opening_prologue_body_label.custom_minimum_size = Vector2(700, 72) opening_prologue_body_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART opening_prologue_body_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER - _apply_label_style(opening_prologue_body_label, UI_AGED_INK, 18) + _apply_label_style(opening_prologue_body_label, UI_AGED_INK, 17) opening_caption_row.add_child(opening_prologue_body_label) var opening_button_column := VBoxContainer.new() - opening_button_column.custom_minimum_size = Vector2(168, 102) - opening_button_column.add_theme_constant_override("separation", 6) + opening_button_column.custom_minimum_size = Vector2(150, 72) + opening_button_column.add_theme_constant_override("separation", 4) opening_caption_row.add_child(opening_button_column) opening_prologue_skip_button = Button.new() opening_prologue_skip_button.text = "건너뛰기" opening_prologue_skip_button.icon = _make_toolbar_icon("cancel") - opening_prologue_skip_button.custom_minimum_size = Vector2(168, 34) + opening_prologue_skip_button.custom_minimum_size = Vector2(150, 30) opening_prologue_skip_button.tooltip_text = "오프닝을 닫고 군막으로 이동합니다." 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.icon = _make_toolbar_icon("move") - opening_prologue_next_button.custom_minimum_size = Vector2(168, 44) + opening_prologue_next_button.custom_minimum_size = Vector2(150, 38) opening_prologue_next_button.tooltip_text = "다음 이야기로 넘깁니다." opening_prologue_next_button.pressed.connect(_on_opening_prologue_next_pressed) opening_button_column.add_child(opening_prologue_next_button) @@ -4306,14 +4311,14 @@ func _create_hud() -> void: title_column.add_child(_make_title_story_preview_strip()) var title_body_panel := PanelContainer.new() - title_body_panel.custom_minimum_size = Vector2(646, 284) + title_body_panel.custom_minimum_size = Vector2(646, 246) _apply_panel_style(title_body_panel, "command_seal") title_column.add_child(title_body_panel) var title_body_row := HBoxContainer.new() title_body_row.add_theme_constant_override("separation", 10) title_body_panel.add_child(title_body_row) - title_body_row.add_child(_make_edict_marker_stack(["", "", ""], 260)) + title_body_row.add_child(_make_edict_marker_stack(["", "", ""], 222)) var title_menu_column := VBoxContainer.new() title_menu_column.add_theme_constant_override("separation", 10) @@ -4544,7 +4549,7 @@ func _create_hud() -> void: title_settings_back_button.pressed.connect(_on_title_settings_back_pressed) title_menu_column.add_child(title_settings_back_button) - title_body_row.add_child(_make_edict_marker_stack(["", "", ""], 260)) + title_body_row.add_child(_make_edict_marker_stack(["", "", ""], 222)) title_column.add_child(_make_scroll_rod(646, 4)) for hud_button in [ @@ -13195,15 +13200,15 @@ func _update_opening_prologue_page() -> void: _set_fitted_label_text( opening_prologue_title_label, str(page.get("title", "")), - 25, - 21, + 22, + 18, opening_prologue_title_label.custom_minimum_size ) if opening_prologue_body_label != null: _set_fitted_label_text( opening_prologue_body_label, str(page.get("body", "")), - 18, + 17, 14, opening_prologue_body_label.custom_minimum_size ) diff --git a/tools/smoke_title_menu.gd b/tools/smoke_title_menu.gd index 3abd043..f3828c7 100644 --- a/tools/smoke_title_menu.gd +++ b/tools/smoke_title_menu.gd @@ -96,12 +96,12 @@ func _check_title_menu_start_load_and_settings(failures: Array[String]) -> void: 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") - elif feature_image.custom_minimum_size.x < 380.0 or feature_image.custom_minimum_size.y < 140.0: - failures.append("title story feature should lead with a wide generated artwork frame: %s" % str(feature_image.custom_minimum_size)) + elif feature_image.custom_minimum_size.x < 430.0 or feature_image.custom_minimum_size.y < 160.0: + failures.append("title story feature should lead with a larger generated artwork frame: %s" % str(feature_image.custom_minimum_size)) 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") + failures.append("title story feature should summarize the first opening beat with a short Korean teaser") 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") @@ -126,7 +126,7 @@ func _check_title_menu_start_load_and_settings(failures: Array[String]) -> void: 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 < 54.0: + elif story_image.custom_minimum_size.y < 64.0: failures.append("title story preview image should stay large enough to read as artwork: %s" % str(story_image.custom_minimum_size)) if story_label == null or story_label.text.strip_edges().is_empty(): failures.append("title story preview card should name its story beat") @@ -252,8 +252,15 @@ func _check_title_menu_start_load_and_settings(failures: Array[String]) -> void: failures.append("opening prologue story artwork should use a wide cinematic frame") elif scene.opening_prologue_scene_texture_rect.custom_minimum_size.x < 1200 or scene.opening_prologue_scene_texture_rect.custom_minimum_size.y < 700: failures.append("opening prologue story artwork should fill the first screen instead of sitting in a small frame") - if scene.opening_prologue_body_label != null and scene.opening_prologue_body_label.custom_minimum_size.x < 500: + if scene.opening_prologue_body_label != null and scene.opening_prologue_body_label.custom_minimum_size.x < 680: failures.append("opening prologue body text should have enough caption width for Korean copy") + var opening_caption: PanelContainer = null + if scene.opening_prologue_root != null: + opening_caption = scene.opening_prologue_root.find_child("OpeningCaptionScroll", true, false) as PanelContainer + if opening_caption == null: + failures.append("opening prologue should keep a bottom scroll caption over the artwork") + elif opening_caption.custom_minimum_size.y > 124.0 or opening_caption.position.y < 540.0: + failures.append("opening prologue caption should stay low and compact so the image leads: %s at %s" % [str(opening_caption.custom_minimum_size), str(opening_caption.position)]) var first_story_texture: Texture2D = null if scene.opening_prologue_scene_texture_rect != null: first_story_texture = scene.opening_prologue_scene_texture_rect.texture @@ -285,6 +292,16 @@ func _check_title_menu_start_load_and_settings(failures: Array[String]) -> void: failures.append("opening prologue should close after advancing through its pages") if scene.briefing_panel == null or not scene.briefing_panel.visible: failures.append("finishing the opening prologue should enter the first briefing") + var audio_scene = BattleSceneScript.new() + audio_scene._ready() + audio_scene._enter_campaign_from_title() + audio_scene._start_battle_from_briefing() + if audio_scene.current_bgm_key != "battle" or audio_scene.bgm_player == null or audio_scene.bgm_player.stream == null: + failures.append("battle start should switch from menu music to an audible battle BGM stream") + audio_scene._show_title_menu() + if audio_scene.current_bgm_key != "menu" or audio_scene.bgm_player == null or audio_scene.bgm_player.stream == null: + failures.append("returning to title should restore the menu BGM stream") + audio_scene.free() if not scene.campaign_state.has_save(): failures.append("starting a new game should create the main campaign save") if not scene.campaign_state.save_manual_game():