diff --git a/README.md b/README.md index e17dfdd..9891606 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Godot 4 tactical RPG prototype inspired by classic turn-based Romance of the Thr - Pre-battle shop rows emphasize item artwork, compact price/status badges, and hover details instead of dense inline text. - Pre-battle Talk can list scenario camp conversations by officer or topic with portrait rows, compact topic/supply badges, hover dialogue previews, saved-branch filtering, one-time pre-battle supplies, and merchant flavor lines before a battle. - The opening battle's camp now includes a villager route report that frames the castle, village recovery point, and enemy approach before combat starts. -- The opening battle now uses a dedicated high-resolution Yingchuan battlefield backdrop, lighter terrain overlays, no text class boxes on units, quieter objective progress text, a tactical lure-line marker, and a pulled-back enemy line so the player can lure and form up before the first clash. +- The opening battle now uses a dedicated high-resolution Yingchuan battlefield backdrop, clearer generated terrain-cell texture overlays, image-first recovery/supply/tactic tile markers, no text class boxes on units, quieter objective progress text, and a pulled-back enemy line so the player can lure and form up before the first clash. - The opening victory dialogue now bridges Xiahou Yuan and Cao Ren's join into the Sishui Gate march against Hua Xiong. - Sishui Gate now opens with camp dialogue that turns the coalition briefing into a spoken march order. - Pre-battle Armory equipment changes, including unequipping gear back into stock, save immediately and sync roster equipment plus inventory stock, with portrait and item-icon rows plus hover change details. @@ -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 Cao Cao decision and Xiahou Dun opening prologue with full-screen story artwork plus 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, 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 df47aff..92bf403 100644 --- a/scripts/scenes/battle_scene.gd +++ b/scripts/scenes/battle_scene.gd @@ -45,7 +45,7 @@ const SETTINGS_SAVE_VERSION := 1 const OPENING_PROLOGUE_PAGES := [ { "title": "황건의 난", - "body": "누런 머릿수건을 맨 무리가 고을을 불태우고 길을 끊었다. 젊은 조조는 무너지는 질서와 굶주린 백성을 보며 더는 관망할 수 없음을 깨달았다.", + "body": "황건의 난이 일어나자 누런 머릿수건을 맨 무리가 고을을 불태우고 길을 끊었다. 젊은 조조는 무너지는 질서와 굶주린 백성을 보며 더는 관망할 수 없음을 깨달았다.", "image": OPENING_PROLOGUE_REBELLION_PATH }, { @@ -2933,6 +2933,21 @@ func _create_hud() -> void: opening_bottom_dim.size = Vector2(1280, 264) 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) + _apply_panel_style(opening_chapter_panel, "caption") + opening_prologue_root.add_child(opening_chapter_panel) + + var opening_chapter_label := Label.new() + 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) + opening_chapter_panel.add_child(opening_chapter_label) + var opening_caption_panel := PanelContainer.new() opening_caption_panel.name = "OpeningCaptionScroll" opening_caption_panel.position = Vector2(56, 500) @@ -4545,6 +4560,8 @@ func _draw_recovery_terrain_markers(bounds: Rect2i) -> void: var marker_text := _recovery_marker_text(cell) if marker_text.is_empty(): continue + var cell_rect := _rect_for_cell(cell) + _draw_tile_marker("recover", cell_rect, Color(1.0, 1.0, 1.0, 0.34), 2.0) var marker_rect := _recovery_marker_rect(cell) draw_rect(marker_rect, Color(0.035, 0.022, 0.012, 0.74)) draw_rect(marker_rect, Color(UI_OLD_BRONZE.r, UI_OLD_BRONZE.g, UI_OLD_BRONZE.b, 0.88), false, 1.4) @@ -4928,7 +4945,7 @@ func _texture_cover_region_source_rect(texture: Texture2D, visible_rect: Rect2, func _terrain_fill_color(cell: Vector2i, terrain_key: String, has_background: bool) -> Color: var color := state.get_terrain_color(cell) - var alpha := 0.075 if has_background else 0.34 + var alpha := 0.105 if has_background else 0.34 if terrain_key == "W": alpha += 0.025 elif terrain_key == "C" or terrain_key == "H" or terrain_key == "T": @@ -5037,7 +5054,7 @@ func _draw_terrain_feature(feature_key: String, rect: Rect2, modulate: Color = C func _terrain_texture_modulate(cell: Vector2i, terrain_key: String, has_background: bool) -> Color: - var alpha := 0.17 if has_background else 0.58 + var alpha := 0.26 if has_background else 0.58 if terrain_key == "W": alpha += 0.07 if has_background else 0.10 elif terrain_key == "C" or terrain_key == "T": @@ -5382,6 +5399,10 @@ func _draw_event_foreground_markers() -> void: func _draw_event_foreground_marker(rect: Rect2, label: String, kind: String, font: Font) -> void: var color := _event_marker_color(kind) + var tile_marker_key := _event_marker_tile_marker_key(label, kind) + if not tile_marker_key.is_empty(): + _draw_tile_marker(tile_marker_key, rect, Color(1.0, 1.0, 1.0, 0.38), 2.0) + draw_rect(rect.grow(-8.0), Color(color.r, color.g, color.b, 0.16), false, 1.5) var marker_rect := _event_marker_rect(rect) draw_rect(marker_rect, Color(0.035, 0.022, 0.012, 0.76)) draw_rect(marker_rect.grow(-2.0), Color(color.r, color.g, color.b, 0.72)) @@ -5420,6 +5441,17 @@ func _event_marker_badge_kind(label: String, kind: String) -> String: return "event" +func _event_marker_tile_marker_key(label: String, kind: String) -> String: + var badge_kind := _event_marker_badge_kind(label, kind) + if badge_kind == "supply": + return "recover" + if badge_kind == "tactic": + return "move" + if badge_kind == "gold" or badge_kind == "objective": + return "objective" + return "select" + + func _event_marker_abbrev(label: String, kind: String) -> String: if kind == "tactic": return "유" if label.contains("유인") else "전" @@ -11695,7 +11727,7 @@ func _update_opening_prologue_page() -> void: if opening_prologue_body_label != null: opening_prologue_body_label.text = str(page.get("body", "")) if opening_prologue_step_label != null: - opening_prologue_step_label.text = "이야기 %d/%d" % [opening_prologue_index + 1, page_count] + opening_prologue_step_label.text = "장면 %d/%d" % [opening_prologue_index + 1, page_count] if opening_prologue_next_button != null: opening_prologue_next_button.text = "군막으로" if opening_prologue_index >= page_count - 1 else "다음" opening_prologue_next_button.tooltip_text = "군막으로 이동합니다." if opening_prologue_index >= page_count - 1 else "다음 이야기로 넘깁니다." diff --git a/tools/smoke_title_menu.gd b/tools/smoke_title_menu.gd index 660f2a4..01e1928 100644 --- a/tools/smoke_title_menu.gd +++ b/tools/smoke_title_menu.gd @@ -144,10 +144,12 @@ func _check_title_menu_start_load_and_settings(failures: Array[String]) -> void: failures.append("opening prologue should hold back the first briefing until advanced") if scene.opening_prologue_title_label == null or scene.opening_prologue_body_label == null: failures.append("opening prologue should expose title and story text") - elif not scene.opening_prologue_body_label.text.contains("조조"): + elif not scene.opening_prologue_body_label.text.contains("조조") or not scene.opening_prologue_body_label.text.contains("황건의 난"): failures.append("opening prologue should first explain Cao Cao before the Yellow Turban battle") - if scene.opening_prologue_step_label == null or not scene.opening_prologue_step_label.text.ends_with("/4"): + if scene.opening_prologue_step_label == null or not scene.opening_prologue_step_label.text.begins_with("장면 ") or not scene.opening_prologue_step_label.text.ends_with("/4"): failures.append("opening prologue should give Cao Cao, decision, Xiahou Dun, and Yingchuan their own beats") + if scene.opening_prologue_root == null or scene.opening_prologue_root.find_child("OpeningChapterSeal", true, false) == null: + failures.append("opening prologue should anchor the generated story art with a chapter seal") if scene.opening_prologue_scene_texture_rect == null or scene.opening_prologue_scene_texture_rect.texture == null: failures.append("opening prologue should show generated story artwork") elif scene.opening_prologue_scene_texture_rect.custom_minimum_size.x <= scene.opening_prologue_scene_texture_rect.custom_minimum_size.y: diff --git a/tools/smoke_visual_assets.gd b/tools/smoke_visual_assets.gd index eff2742..aaca0f5 100644 --- a/tools/smoke_visual_assets.gd +++ b/tools/smoke_visual_assets.gd @@ -4413,6 +4413,14 @@ func _check_terrain_and_unit_presentation(failures: Array[String]) -> void: failures.append("Gold event markers should use the generated gold badge") if scene._event_marker_badge_kind("유인선", "tactic") != "tactic": failures.append("Tactic event markers should use the generated tactic badge") + if scene._event_marker_tile_marker_key("마을 보급", "supply") != "recover": + failures.append("Supply event markers should lay generated recovery tile art under the badge") + if scene._event_marker_tile_marker_key("유인선", "tactic") != "move": + failures.append("Tactic lure markers should lay generated movement tile art under the badge") + if scene._event_marker_tile_marker_key("군자금", "gold") != "objective": + failures.append("Gold markers should use a generated objective tile frame") + if scene._event_marker_tile_marker_key("", "") != "select": + failures.append("Unknown event markers should still use a generated tile frame") if scene._event_marker_badge_kind("군자금", "") != "gold": failures.append("Event marker labels should infer generated badge kind when kind is absent") if scene._event_marker_badge_kind("", "") != "event": @@ -4423,6 +4431,12 @@ func _check_terrain_and_unit_presentation(failures: Array[String]) -> void: failures.append("background terrain fill should stay translucent over high-res art") if fallback_fill.a <= background_fill.a: failures.append("fallback terrain fill should be stronger than background fill") + var background_texture_modulate: Color = scene._terrain_texture_modulate(Vector2i(0, 0), "G", true) + var fallback_texture_modulate: Color = scene._terrain_texture_modulate(Vector2i(0, 0), "G", false) + if background_texture_modulate.a < 0.22: + failures.append("background terrain texture should be visible enough to make generated cells read as art: %.3f" % background_texture_modulate.a) + if background_texture_modulate.a >= fallback_texture_modulate.a: + failures.append("background terrain texture should stay lighter than fallback terrain texture") if scene._map_grid_color(true).a >= scene._map_grid_color(false).a: failures.append("background grid should be lighter than fallback grid") if scene._terrain_edge_color("F", "G").a <= 0.0: