From 8ba83cc9857cfe29587bad27bd0adcaeac37db4f Mon Sep 17 00:00:00 2001 From: Wickedness Date: Fri, 19 Jun 2026 13:01:03 +0900 Subject: [PATCH] Improve opening battle layout and scouting brief --- scripts/core/battle_state.gd | 28 ++++++++++++++ scripts/scenes/battle_scene.gd | 62 +++++++++++++++++++++++++++++-- tools/smoke_objective_progress.gd | 33 ++++++++++++++++ 3 files changed, 120 insertions(+), 3 deletions(-) diff --git a/scripts/core/battle_state.gd b/scripts/core/battle_state.gd index 4190bf5..b3a83e1 100644 --- a/scripts/core/battle_state.gd +++ b/scripts/core/battle_state.gd @@ -2029,6 +2029,34 @@ func get_terrain_key(cell: Vector2i) -> String: return row.substr(cell.x, 1) +func get_map_size() -> Vector2i: + return map_size + + +func get_recovery_terrain_summaries() -> Array[Dictionary]: + var summaries: Array[Dictionary] = [] + var seen := {} + for y in range(map_size.y): + for x in range(map_size.x): + var cell := Vector2i(x, y) + var heal := get_terrain_heal(cell) + if heal <= 0: + continue + var key := get_terrain_key(cell) + if seen.has(key): + seen[key]["count"] = int(seen[key].get("count", 0)) + 1 + continue + var summary := { + "key": key, + "name": get_terrain_name(cell), + "heal": heal, + "count": 1 + } + seen[key] = summary + summaries.append(summary) + return summaries + + func get_map_background_path() -> String: return map_background diff --git a/scripts/scenes/battle_scene.gd b/scripts/scenes/battle_scene.gd index d32078e..b78ee30 100644 --- a/scripts/scenes/battle_scene.gd +++ b/scripts/scenes/battle_scene.gd @@ -4,8 +4,10 @@ const BattleStateScript := preload("res://scripts/core/battle_state.gd") const CampaignStateScript := preload("res://scripts/core/campaign_state.gd") const CAMPAIGN_PATH := "res://data/campaign/campaign.json" -const TILE_SIZE := 64 +const TILE_SIZE := 54 const BOARD_OFFSET := Vector2(48, 104) +const SIDE_PANEL_POSITION := Vector2(824, 104) +const SIDE_PANEL_SIZE := Vector2(424, 610) const DEFAULT_BATTLE_BACKGROUND_PATH := "res://art/backgrounds/battlefield_frontier.png" const PLAYER_COLOR := Color(0.17, 0.38, 0.29) const ENEMY_COLOR := Color(0.64, 0.085, 0.045) @@ -1105,8 +1107,8 @@ func _create_hud() -> void: objective_notice_column.add_child(_make_scroll_rod(548, 3)) var side_panel := PanelContainer.new() - side_panel.position = Vector2(760, 104) - side_panel.size = Vector2(480, 610) + side_panel.position = SIDE_PANEL_POSITION + side_panel.size = SIDE_PANEL_SIZE _apply_panel_style(side_panel, "compact") root.add_child(side_panel) @@ -5419,6 +5421,9 @@ func _format_briefing_camp_overview_text(briefing: Dictionary, prep_locked: bool var location := str(briefing.get("location", "")).strip_edges() if not location.is_empty(): lines.append("위치: %s" % location) + var battlefield_text := _format_briefing_battlefield_overview_text() + if not battlefield_text.is_empty(): + lines.append("전장: %s" % battlefield_text) var camp_parts := ["군자금 %d냥" % campaign_state.gold] var talk_text := _format_briefing_talk_overview_text() if not talk_text.is_empty(): @@ -5444,6 +5449,57 @@ func _format_briefing_camp_overview_text(briefing: Dictionary, prep_locked: bool return _join_strings(lines, "\n") +func _format_briefing_battlefield_overview_text() -> String: + var parts := [] + var battle_map_size := state.get_map_size() + if battle_map_size.x > 0 and battle_map_size.y > 0: + parts.append("%d칸 x %d칸" % [battle_map_size.x, battle_map_size.y]) + var enemy_text := _format_briefing_enemy_force_text() + if not enemy_text.is_empty(): + parts.append(enemy_text) + var recovery_text := _format_briefing_recovery_terrain_text() + if not recovery_text.is_empty(): + parts.append(recovery_text) + return _join_strings(parts, " · ") + + +func _format_briefing_enemy_force_text() -> String: + var enemies := state.get_living_units(BattleState.TEAM_ENEMY) + if enemies.is_empty(): + return "" + var class_order: Array[String] = [] + var class_counts := {} + for enemy in enemies: + var class_label := _unit_class_display_name(enemy) + if class_label.is_empty(): + class_label = "부대" + if not class_counts.has(class_label): + class_order.append(class_label) + class_counts[class_label] = 0 + class_counts[class_label] = int(class_counts[class_label]) + 1 + var class_parts := [] + for class_label in class_order: + class_parts.append("%s %d" % [class_label, int(class_counts.get(class_label, 0))]) + return "적세 %d명 (%s)" % [enemies.size(), _join_strings(class_parts, ", ")] + + +func _format_briefing_recovery_terrain_text() -> String: + var recovery_summaries := state.get_recovery_terrain_summaries() + if recovery_summaries.is_empty(): + return "" + var parts := [] + for summary in recovery_summaries: + var name := _terrain_display_name(str(summary.get("name", "지형"))) + var count := int(summary.get("count", 0)) + var heal := int(summary.get("heal", 0)) + if count <= 0 or heal <= 0: + continue + parts.append("%s %d칸 +%d" % [name, count, heal]) + if parts.is_empty(): + return "" + return "회복지 %s" % _join_strings(parts, ", ") + + func _format_briefing_talk_overview_text() -> String: var conversations := _camp_conversation_entries() if conversations.is_empty(): diff --git a/tools/smoke_objective_progress.gd b/tools/smoke_objective_progress.gd index 377eca3..d0eac1b 100644 --- a/tools/smoke_objective_progress.gd +++ b/tools/smoke_objective_progress.gd @@ -68,6 +68,8 @@ func _init() -> void: _check_objective_update(failures) _check_objective_notice(failures) _check_terrain_recovery(failures) + _check_briefing_battlefield_overview(failures) + _check_opening_board_avoids_side_panel(failures) if failures.is_empty(): print("objective progress smoke ok") @@ -254,6 +256,37 @@ func _check_terrain_recovery(failures: Array[String]) -> void: failures.append("castle terrain should recover 8 hp at phase start, got %d" % int(boss.get("hp", 0))) +func _check_briefing_battlefield_overview(failures: Array[String]) -> void: + var scene = BattleSceneScript.new() + if not scene.state.load_battle("res://data/scenarios/001_yellow_turbans.json"): + failures.append("briefing battlefield overview smoke could not load scenario") + scene.free() + return + var overview := scene._format_briefing_battlefield_overview_text() + _check_contains(failures, "001 briefing map size", overview, "14칸 x 10칸") + _check_contains(failures, "001 briefing enemy count", overview, "적세 8명") + _check_contains(failures, "001 briefing enemy classes", overview, "보병") + _check_contains(failures, "001 briefing village recovery", overview, "마을 4칸 +6") + _check_contains(failures, "001 briefing castle recovery", overview, "성채 4칸 +8") + scene.free() + + +func _check_opening_board_avoids_side_panel(failures: Array[String]) -> void: + var state = BattleStateScript.new() + if not state.load_battle("res://data/scenarios/001_yellow_turbans.json"): + failures.append("opening board layout smoke could not load scenario") + return + var board_rect := Rect2( + BattleSceneScript.BOARD_OFFSET, + Vector2(state.get_map_size()) * BattleSceneScript.TILE_SIZE + ) + var side_rect := Rect2(BattleSceneScript.SIDE_PANEL_POSITION, BattleSceneScript.SIDE_PANEL_SIZE) + if board_rect.intersects(side_rect): + failures.append("opening board should not overlap side HUD: board %s side %s" % [str(board_rect), str(side_rect)]) + if board_rect.end.y > 720.0: + failures.append("opening board should fit within the default viewport height: %s" % str(board_rect)) + + func _check_objective_notice(failures: Array[String]) -> void: var scene = BattleSceneScript.new() scene._create_hud()