From 9d31448573b6377dabca741d839e696cbe4b18ef Mon Sep 17 00:00:00 2001 From: Wickedness Date: Fri, 19 Jun 2026 02:47:18 +0900 Subject: [PATCH] Age battle UI toward ancient campaign style --- scripts/scenes/battle_scene.gd | 158 +++++++++++++++++++++++------- tools/smoke_objective_progress.gd | 12 +-- tools/smoke_visual_assets.gd | 23 ++++- 3 files changed, 146 insertions(+), 47 deletions(-) diff --git a/scripts/scenes/battle_scene.gd b/scripts/scenes/battle_scene.gd index 0bc3756..605a94b 100644 --- a/scripts/scenes/battle_scene.gd +++ b/scripts/scenes/battle_scene.gd @@ -83,6 +83,9 @@ const UI_BAMBOO_DARK := Color(0.20, 0.11, 0.045, 1.0) const UI_MUTED_JADE := Color(0.16, 0.30, 0.23, 1.0) const UI_PARCHMENT_TEXT := Color(0.95, 0.82, 0.55, 1.0) const UI_DISABLED_TEXT := Color(0.48, 0.40, 0.29, 1.0) +const UI_SILK_WASH := Color(0.80, 0.64, 0.37, 1.0) +const UI_SILK_FIBER := Color(0.18, 0.10, 0.045, 1.0) +const MAP_SCROLL_FRAME_PADDING := 12.0 var state: BattleState = BattleStateScript.new() var campaign_state: CampaignState = CampaignStateScript.new() @@ -431,6 +434,27 @@ func _make_bamboo_divider(width: float = 0.0, height: float = 4.0) -> HBoxContai return row +func _make_scroll_rod(width: float = 0.0, height: float = 4.0) -> HBoxContainer: + var row := HBoxContainer.new() + row.custom_minimum_size = Vector2(width, height) + row.size_flags_horizontal = Control.SIZE_EXPAND_FILL + row.add_theme_constant_override("separation", 2) + var cap_width: float = maxf(8.0, height * 3.0) + for index in range(5): + var strip := ColorRect.new() + strip.mouse_filter = Control.MOUSE_FILTER_IGNORE + strip.custom_minimum_size = Vector2(cap_width if index != 2 else width, height) + strip.size_flags_horizontal = Control.SIZE_EXPAND_FILL if index == 2 else Control.SIZE_SHRINK_CENTER + if index == 0 or index == 4: + strip.color = UI_CINNABAR_DARK + elif index == 1 or index == 3: + strip.color = UI_OLD_BRONZE + else: + strip.color = UI_BAMBOO + row.add_child(strip) + return row + + func _make_section_caption(text: String, width: float = 0.0) -> Label: var label := Label.new() label.text = text @@ -558,7 +582,7 @@ func _create_hud() -> void: selected_row.add_child(selected_label) mission_title_label = Label.new() - mission_title_label.text = "Battle Mandate" + mission_title_label.text = "Imperial Mandate" mission_title_label.add_theme_font_size_override("font_size", 14) _apply_label_style(mission_title_label, UI_OLD_BRONZE) side_column.add_child(mission_title_label) @@ -846,8 +870,8 @@ func _create_hud() -> void: _apply_label_style(briefing_location_label, UI_MUTED_JADE, 14) briefing_column.add_child(briefing_location_label) - briefing_column.add_child(_make_bamboo_divider(680, 4)) - briefing_column.add_child(_make_section_caption("Bronze War Edict", 680)) + briefing_column.add_child(_make_scroll_rod(680, 4)) + briefing_column.add_child(_make_section_caption("Imperial Bronze War Edict", 680)) briefing_objective_panel = PanelContainer.new() briefing_objective_panel.custom_minimum_size = Vector2(680, 92) @@ -867,7 +891,7 @@ func _create_hud() -> void: _apply_label_style(briefing_objective_label, UI_AGED_INK, 15) briefing_objective_row.add_child(briefing_objective_label) - briefing_column.add_child(_make_section_caption("Bamboo Map and Camp Ledger", 680)) + briefing_column.add_child(_make_section_caption("Silk Map and Camp Ledger", 680)) briefing_camp_overview_row = HBoxContainer.new() briefing_camp_overview_row.custom_minimum_size = Vector2(680, 84) @@ -905,7 +929,7 @@ func _create_hud() -> void: _apply_label_style(briefing_camp_overview_label, UI_AGED_INK, 14) briefing_camp_overview_row.add_child(briefing_camp_overview_label) - briefing_column.add_child(_make_bamboo_divider(680, 3)) + briefing_column.add_child(_make_scroll_rod(680, 3)) var briefing_scroll := ScrollContainer.new() briefing_scroll.custom_minimum_size = Vector2(680, 66) @@ -1116,7 +1140,7 @@ func _create_hud() -> void: save_action_row.add_child(manual_load_button) var begin_button := Button.new() - begin_button.text = "Enter Field" + begin_button.text = "Unroll War Map" begin_button.pressed.connect(_on_begin_battle_pressed) briefing_column.add_child(begin_button) @@ -1161,7 +1185,7 @@ func _create_hud() -> void: dialogue_column.add_theme_constant_override("separation", 8) dialogue_row.add_child(dialogue_column) - dialogue_column.add_child(_make_bamboo_divider(DIALOGUE_TEXT_SIZE.x, 3)) + dialogue_column.add_child(_make_scroll_rod(DIALOGUE_TEXT_SIZE.x, 3)) dialogue_speaker_panel = PanelContainer.new() dialogue_speaker_panel.custom_minimum_size = Vector2(DIALOGUE_TEXT_SIZE.x, 32) @@ -1193,7 +1217,7 @@ func _create_hud() -> void: _apply_label_style(dialogue_text_label, UI_AGED_INK, 17) dialogue_text_row.add_child(dialogue_text_label) - dialogue_column.add_child(_make_bamboo_divider(DIALOGUE_TEXT_SIZE.x, 3)) + dialogue_column.add_child(_make_scroll_rod(DIALOGUE_TEXT_SIZE.x, 3)) var dialogue_control_row := HBoxContainer.new() dialogue_control_row.add_theme_constant_override("separation", 8) @@ -1230,6 +1254,8 @@ func _create_hud() -> void: result_column.alignment = BoxContainer.ALIGNMENT_CENTER result_panel.add_child(result_column) + result_column.add_child(_make_scroll_rod(520, 4)) + result_label = Label.new() result_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_LEFT result_label.vertical_alignment = VERTICAL_ALIGNMENT_TOP @@ -1238,6 +1264,8 @@ func _create_hud() -> void: _apply_label_style(result_label, UI_AGED_INK, 16) result_column.add_child(result_label) + result_column.add_child(_make_scroll_rod(520, 3)) + result_choice_list = VBoxContainer.new() result_choice_list.add_theme_constant_override("separation", 6) result_column.add_child(result_choice_list) @@ -1759,7 +1787,58 @@ func _draw_map() -> void: _draw_terrain_edge_blend(cell, rect, terrain_key) draw_rect(rect, _map_grid_color(has_background), false, 1.0) - draw_rect(board_rect, Color(0.02, 0.025, 0.03), false, 3.0) + _draw_silk_map_patina(board_rect, has_background) + _draw_silk_map_frame(board_rect) + + +func _draw_silk_map_patina(board_rect: Rect2, has_background: bool) -> void: + var wash := UI_SILK_WASH + wash.a = 0.08 if has_background else 0.14 + draw_rect(board_rect, wash) + var fiber := UI_SILK_FIBER + fiber.a = 0.12 if has_background else 0.18 + for index in range(7): + var y := board_rect.position.y + 18.0 + float(index) * (board_rect.size.y - 36.0) / 6.0 + var drift := 4.0 + _cell_noise(Vector2i(index, state.map_size.y), 211) * 10.0 + draw_line( + Vector2(board_rect.position.x + 12.0, y), + Vector2(board_rect.end.x - 12.0, y + drift), + fiber, + 1.0 + ) + for index in range(5): + var x := board_rect.position.x + 20.0 + float(index) * (board_rect.size.x - 40.0) / 4.0 + var drift := 5.0 + _cell_noise(Vector2i(state.map_size.x, index), 227) * 9.0 + draw_line( + Vector2(x, board_rect.position.y + 12.0), + Vector2(x + drift, board_rect.end.y - 12.0), + Color(fiber.r, fiber.g, fiber.b, fiber.a * 0.65), + 1.0 + ) + var corner_stain := Color(0.12, 0.06, 0.025, 0.12 if has_background else 0.18) + draw_circle(board_rect.position + Vector2(20, 18), 26.0, corner_stain) + draw_circle(board_rect.end - Vector2(24, 22), 30.0, corner_stain) + + +func _draw_silk_map_frame(board_rect: Rect2) -> void: + var padding := MAP_SCROLL_FRAME_PADDING + var top_rod := Rect2(board_rect.position + Vector2(-padding, -padding), Vector2(board_rect.size.x + padding * 2.0, 7.0)) + var bottom_rod := Rect2(Vector2(board_rect.position.x - padding, board_rect.end.y + padding - 7.0), top_rod.size) + var left_rod := Rect2(board_rect.position + Vector2(-padding, -padding), Vector2(7.0, board_rect.size.y + padding * 2.0)) + var right_rod := Rect2(Vector2(board_rect.end.x + padding - 7.0, board_rect.position.y - padding), left_rod.size) + for rod in [top_rod, bottom_rod, left_rod, right_rod]: + draw_rect(rod, UI_BAMBOO_DARK) + draw_rect(rod, Color(0.62, 0.43, 0.18, 0.80), false, 1.0) + for corner in [ + top_rod.position - Vector2(3, 3), + Vector2(top_rod.end.x - 11.0, top_rod.position.y - 3.0), + Vector2(bottom_rod.position.x - 3.0, bottom_rod.end.y - 11.0), + bottom_rod.end - Vector2(11, 11) + ]: + draw_rect(Rect2(corner, Vector2(14, 14)), UI_CINNABAR_DARK) + draw_rect(Rect2(corner + Vector2(2, 2), Vector2(10, 10)), UI_OLD_BRONZE, false, 1.0) + draw_rect(board_rect.grow(4.0), UI_LACQUER_EDGE, false, 5.0) + draw_rect(board_rect.grow(1.0), UI_OLD_BRONZE, false, 2.0) func _current_battle_background_texture() -> Texture2D: @@ -2964,8 +3043,8 @@ func _update_hud() -> void: func _format_objective_hud_text() -> String: var text := String(state.objectives.get("victory", "Defeat all enemies.")) if text.is_empty(): - return "Battle Mandate" - return "Battle Mandate: %s" % text + return "Imperial Mandate" + return "Imperial Mandate: %s" % text func _format_unit_focus_text(unit: Dictionary, prefix: String) -> String: @@ -3135,19 +3214,19 @@ func _format_mission_panel_text() -> String: var lines := [] var victory := str(state.objectives.get("victory", "")) if not victory.is_empty(): - lines.append("Edict: %s" % victory) + lines.append("Imperial Edict: %s" % victory) var progress_lines := state.get_objective_progress_lines(false, false) if not progress_lines.is_empty(): - lines.append("Battle Register:") + lines.append("Bamboo Register:") for progress_line in progress_lines: lines.append(" %s" % progress_line) var risk_lines := state.get_defeat_progress_lines() if not risk_lines.is_empty(): - lines.append("Dire Portents:") + lines.append("Ominous Signs:") for risk_line in risk_lines: lines.append(" %s" % risk_line) if lines.is_empty(): - return "No battle mandate." + return "No edict has been sealed." return _join_strings(lines, "\n") @@ -3542,10 +3621,7 @@ func _update_result_panel() -> void: if result_restart_button != null: result_restart_button.visible = true _update_result_next_button_visibility() - result_label.text = "Victory Proclamation\n%s\n%s" % [ - state.objectives.get("victory", ""), - _format_battle_result_summary() - ] + result_label.text = _format_victory_result_text() elif state.battle_status == BattleState.STATUS_DEFEAT: _clear_dialogue() _clear_result_choices() @@ -3554,7 +3630,7 @@ func _update_result_panel() -> void: result_restart_button.visible = true if next_battle_button != null: next_battle_button.visible = false - result_label.text = "Defeat Memorial\n%s" % state.objectives.get("defeat", "") + result_label.text = _format_defeat_result_text() else: _clear_result_choices() result_panel.visible = false @@ -3564,6 +3640,17 @@ func _update_result_panel() -> void: next_battle_button.visible = false +func _format_victory_result_text() -> String: + return "Bronze Victory Proclamation\nMandate Fulfilled: %s\n%s" % [ + state.objectives.get("victory", ""), + _format_battle_result_summary() + ] + + +func _format_defeat_result_text() -> String: + return "Defeat Memorial Tablet\nOmen Fulfilled: %s" % state.objectives.get("defeat", "") + + func _on_state_changed() -> void: if state.get_selected_unit().is_empty(): selected_skill_id = "" @@ -3584,18 +3671,18 @@ func _on_log_added(message: String) -> void: func _format_log_entry_text(message: String) -> String: if message.begins_with("Objective updated:"): - return "Mandate revised:%s" % message.substr("Objective updated:".length()) + return "Edict amended:%s" % message.substr("Objective updated:".length()) if message.begins_with("Defeat condition updated:"): - return "Omen revised:%s" % message.substr("Defeat condition updated:".length()) + return "Omen amended:%s" % message.substr("Defeat condition updated:".length()) return message func _on_objective_updated(victory: String, defeat: String) -> void: - var notice_lines := ["New Battle Mandate"] + var notice_lines := ["New Sealed Edict"] if not victory.is_empty(): - notice_lines.append("Mandate: %s" % victory) + notice_lines.append("Imperial Mandate: %s" % victory) if not defeat.is_empty(): - notice_lines.append("Omen: %s" % defeat) + notice_lines.append("Defeat Omen: %s" % defeat) if objective_notice_label != null: objective_notice_label.text = "\n".join(notice_lines) if objective_notice_panel != null: @@ -4317,27 +4404,27 @@ func _format_briefing_objectives() -> String: var text := "" var victory := str(state.objectives.get("victory", "")) if not victory.is_empty(): - text = "Mandate of Victory: %s" % victory + text = "Imperial Victory Edict: %s" % victory var defeat := str(state.objectives.get("defeat", "")) if not defeat.is_empty(): if not text.is_empty(): text += "\n" - text += "Omen of Ruin: %s" % defeat + text += "Defeat Omen: %s" % defeat var progress_lines := state.get_objective_progress_lines(false, false) if not progress_lines.is_empty(): if not text.is_empty(): text += "\n" - text += "Battle Register: %s" % _join_strings(progress_lines, ", ") + text += "Bamboo Battle Register: %s" % _join_strings(progress_lines, ", ") var risk_lines := state.get_defeat_progress_lines() if not risk_lines.is_empty(): if not text.is_empty(): text += "\n" - text += "Dire Portents: %s" % _join_strings(risk_lines, ", ") + text += "Ominous Signs: %s" % _join_strings(risk_lines, ", ") var rewards_text := _format_briefing_rewards() if not rewards_text.is_empty(): if not text.is_empty(): text += "\n" - text += "Spoils Ledger: %s" % rewards_text + text += "Spoils Seal: %s" % rewards_text return text @@ -6022,10 +6109,7 @@ func _on_post_battle_choice_pressed(choice: Dictionary) -> void: _rebuild_result_choices() _update_result_next_button_visibility() if result_label != null and state.battle_status == BattleState.STATUS_VICTORY: - result_label.text = "Victory Proclamation\n%s\n%s" % [ - state.objectives.get("victory", ""), - _format_battle_result_summary() - ] + result_label.text = _format_victory_result_text() _update_hud() @@ -6899,8 +6983,8 @@ func _action_phase_block_reason() -> Dictionary: } if not battle_started: return { - "label": "Enter Field", - "tooltip": "Begin the battle before using combat actions." + "label": "Unroll Map", + "tooltip": "Unroll the war map before issuing combat orders." } if state.battle_status != BattleState.STATUS_ACTIVE: return { @@ -6954,7 +7038,7 @@ func _update_threat_button() -> void: _set_action_button_blocked(threat_button, "Enemy Lines", "Chronicle Sealed", "The campaign is complete.") return if not battle_started: - _set_action_button_blocked(threat_button, "Enemy Lines", "Enter Field", "Begin the battle before reading enemy lines.") + _set_action_button_blocked(threat_button, "Enemy Lines", "Unroll Map", "Unroll the war map before reading enemy lines.") return if state.battle_status != BattleState.STATUS_ACTIVE: _set_action_button_blocked(threat_button, "Enemy Lines", "Field Settled", "Enemy lines are only available during active battles.") diff --git a/tools/smoke_objective_progress.gd b/tools/smoke_objective_progress.gd index e1ac944..8496109 100644 --- a/tools/smoke_objective_progress.gd +++ b/tools/smoke_objective_progress.gd @@ -211,16 +211,16 @@ func _check_objective_notice(failures: Array[String]) -> void: scene._on_objective_updated("Defeat the new vanguard.", "") if scene.objective_notice_panel == null or not scene.objective_notice_panel.visible: failures.append("objective notice panel should be visible after objective update") - elif not scene.objective_notice_label.text.contains("New Battle Mandate") or not scene.objective_notice_label.text.contains("Mandate: Defeat the new vanguard."): - failures.append("objective notice label should use battle mandate wording: %s" % scene.objective_notice_label.text) + elif not scene.objective_notice_label.text.contains("New Sealed Edict") or not scene.objective_notice_label.text.contains("Imperial Mandate: Defeat the new vanguard."): + failures.append("objective notice label should use sealed edict wording: %s" % scene.objective_notice_label.text) scene._on_objective_updated("", "Supply train is lost.") if scene.objective_notice_panel == null or not scene.objective_notice_panel.visible: failures.append("objective notice panel should be visible after defeat update") - elif not scene.objective_notice_label.text.contains("New Battle Mandate") or not scene.objective_notice_label.text.contains("Omen: Supply train is lost."): + elif not scene.objective_notice_label.text.contains("New Sealed Edict") or not scene.objective_notice_label.text.contains("Defeat Omen: Supply train is lost."): failures.append("objective notice label should use omen wording: %s" % scene.objective_notice_label.text) - if scene._format_log_entry_text("Objective updated: Defeat the new vanguard.") != "Mandate revised: Defeat the new vanguard.": - failures.append("objective log display should use mandate wording") - if scene._format_log_entry_text("Defeat condition updated: Supply train is lost.") != "Omen revised: Supply train is lost.": + if scene._format_log_entry_text("Objective updated: Defeat the new vanguard.") != "Edict amended: Defeat the new vanguard.": + failures.append("objective log display should use edict wording") + if scene._format_log_entry_text("Defeat condition updated: Supply train is lost.") != "Omen amended: Supply train is lost.": failures.append("defeat log display should use omen wording") if scene.objective_notice_timer <= 0.0: failures.append("objective notice timer should be active") diff --git a/tools/smoke_visual_assets.gd b/tools/smoke_visual_assets.gd index 140f89f..734827a 100644 --- a/tools/smoke_visual_assets.gd +++ b/tools/smoke_visual_assets.gd @@ -1516,9 +1516,16 @@ func _check_ancient_ui_theme(failures: Array[String]) -> void: scene.free() return var briefing_text := scene._format_briefing_objectives() - for expected in ["Mandate of Victory:", "Omen of Ruin:", "Battle Register:", "Dire Portents:", "Spoils Ledger:"]: + for expected in ["Imperial Victory Edict:", "Defeat Omen:", "Bamboo Battle Register:", "Ominous Signs:", "Spoils Seal:"]: if not briefing_text.contains(expected): failures.append("briefing objective parchment should use ancient heading `%s`: %s" % [expected, briefing_text]) + var hud_objective := scene._format_objective_hud_text() + if not hud_objective.begins_with("Imperial Mandate:"): + failures.append("HUD objective should read as an imperial mandate: %s" % hud_objective) + var mission_text := scene._format_mission_panel_text() + for expected in ["Imperial Edict:", "Bamboo Register:", "Ominous Signs:"]: + if not mission_text.contains(expected): + failures.append("mission panel should use old campaign wording `%s`: %s" % [expected, mission_text]) _check_panel_style_fill(failures, scene.briefing_panel, "briefing panel", Color(0.58, 0.47, 0.29, 0.985)) _check_panel_style_frame(failures, scene.briefing_panel, "briefing panel", Color(0.09, 0.055, 0.032, 1.0), 4) _check_panel_style_fill(failures, scene.briefing_objective_panel, "briefing objective edict panel", Color(0.73, 0.60, 0.38, 0.99)) @@ -1532,8 +1539,8 @@ func _check_ancient_ui_theme(failures: Array[String]) -> void: failures.append("dialogue continue button should use ancient slip text") if scene.dialogue_previous_button == null or scene.dialogue_previous_button.text != "Earlier Slip": failures.append("dialogue previous button should use ancient slip text") - if scene.mission_title_label == null or scene.mission_title_label.text != "Battle Mandate": - failures.append("mission panel title should use battle mandate wording") + if scene.mission_title_label == null or scene.mission_title_label.text != "Imperial Mandate": + failures.append("mission panel title should use imperial mandate wording") if scene.shop_button == null or scene.shop_button.text != "Sutler": failures.append("briefing shop button should use sutler wording") if scene.talk_button == null or scene.talk_button.text != "War Council": @@ -1554,6 +1561,14 @@ func _check_ancient_ui_theme(failures: Array[String]) -> void: if scene.briefing_camp_overview_fallback_label == null or scene.briefing_camp_overview_fallback_label.text != "Silk War Map": failures.append("briefing map fallback should read as an old campaign map") _check_panel_style_fill(failures, scene.result_panel, "result panel", Color(0.58, 0.47, 0.29, 0.985)) + if not scene._format_victory_result_text().contains("Bronze Victory Proclamation"): + failures.append("victory result should read like a bronze proclamation") + if not scene._format_defeat_result_text().contains("Defeat Memorial Tablet"): + failures.append("defeat result should read like a memorial tablet") + var scroll_rod := scene._make_scroll_rod(120, 4) + if scroll_rod.get_child_count() != 5: + failures.append("scroll rod ornament should use five visual strips") + scroll_rod.free() scene.free() @@ -1654,7 +1669,7 @@ func _check_action_button_disabled_reasons(failures: Array[String]) -> void: scene.state.current_team = BattleState.TEAM_PLAYER scene.battle_started = false scene._update_threat_button() - if scene.threat_button.text != "Enemy Lines - Enter Field" or not scene.threat_button.tooltip_text.to_lower().contains("begin"): + if scene.threat_button.text != "Enemy Lines - Unroll Map" or not scene.threat_button.tooltip_text.to_lower().contains("unroll"): failures.append("inactive battle should explain disabled threat button: %s | %s" % [scene.threat_button.text, scene.threat_button.tooltip_text]) _free_action_button_test_scene(scene)