Clarify combat result reports

This commit is contained in:
2026-06-21 01:45:30 +09:00
parent 6cc5db09fc
commit ddec5b5e94
5 changed files with 298 additions and 4 deletions

View File

@@ -7,6 +7,7 @@ signal changed
signal log_added(message: String) signal log_added(message: String)
signal dialogue_requested(lines: Array) signal dialogue_requested(lines: Array)
signal combat_feedback_requested(unit_id: String, text: String, kind: String) signal combat_feedback_requested(unit_id: String, text: String, kind: String)
signal combat_result_reported(report: Dictionary)
signal unit_motion_requested(unit_id: String, from_cell: Vector2i, to_cell: Vector2i) signal unit_motion_requested(unit_id: String, from_cell: Vector2i, to_cell: Vector2i)
signal unit_action_motion_requested(unit_id: String, from_cell: Vector2i, to_cell: Vector2i, action_kind: String) signal unit_action_motion_requested(unit_id: String, from_cell: Vector2i, to_cell: Vector2i, action_kind: String)
signal objective_updated(victory: String, defeat: String) signal objective_updated(victory: String, defeat: String)
@@ -3752,19 +3753,40 @@ func _opposing_team(team: String) -> String:
func _resolve_combat(attacker: Dictionary, target: Dictionary) -> void: func _resolve_combat(attacker: Dictionary, target: Dictionary) -> void:
var report := _make_combat_result_report(attacker, target)
var attack_result := _resolve_attack(attacker, target, false) var attack_result := _resolve_attack(attacker, target, false)
(report["entries"] as Array).append(attack_result)
var attack_exp := EXP_ATTACK if bool(attack_result.get("hit", false)) else EXP_MISS var attack_exp := EXP_ATTACK if bool(attack_result.get("hit", false)) else EXP_MISS
if bool(attack_result.get("defeated", false)) or battle_status != STATUS_ACTIVE: if bool(attack_result.get("defeated", false)) or battle_status != STATUS_ACTIVE:
_grant_experience(attacker, attack_exp + (EXP_DEFEAT_BONUS if bool(attack_result.get("defeated", false)) else 0)) var final_attack_exp := attack_exp + (EXP_DEFEAT_BONUS if bool(attack_result.get("defeated", false)) else 0)
_grant_experience(attacker, final_attack_exp)
report["attacker_merit"] = final_attack_exp
combat_result_reported.emit(report)
return return
var counter_exp := 0 var counter_exp := 0
if bool(target.get("controllable", true)) and _is_in_attack_range(target, attacker["pos"]): if bool(target.get("controllable", true)) and _is_in_attack_range(target, attacker["pos"]):
var counter_result := _resolve_attack(target, attacker, true) var counter_result := _resolve_attack(target, attacker, true)
(report["entries"] as Array).append(counter_result)
counter_exp = (EXP_COUNTER if bool(counter_result.get("hit", false)) else EXP_MISS) + (EXP_DEFEAT_BONUS if bool(counter_result.get("defeated", false)) else 0) counter_exp = (EXP_COUNTER if bool(counter_result.get("hit", false)) else EXP_MISS) + (EXP_DEFEAT_BONUS if bool(counter_result.get("defeated", false)) else 0)
_grant_experience(attacker, attack_exp) _grant_experience(attacker, attack_exp)
report["attacker_merit"] = attack_exp
_grant_experience(target, counter_exp) _grant_experience(target, counter_exp)
report["counter_merit"] = counter_exp
combat_result_reported.emit(report)
func _make_combat_result_report(attacker: Dictionary, target: Dictionary) -> Dictionary:
return {
"attacker_id": str(attacker.get("id", "")),
"attacker_name": str(attacker.get("name", "")),
"target_id": str(target.get("id", "")),
"target_name": str(target.get("name", "")),
"attacker_merit": 0,
"counter_merit": 0,
"entries": []
}
func _resolve_attack(attacker: Dictionary, target: Dictionary, is_counter := false) -> Dictionary: func _resolve_attack(attacker: Dictionary, target: Dictionary, is_counter := false) -> Dictionary:
@@ -3780,7 +3802,22 @@ func _resolve_attack(attacker: Dictionary, target: Dictionary, is_counter := fal
if rng.randi_range(1, 100) > hit_chance: if rng.randi_range(1, 100) > hit_chance:
_emit_log("%s %s %s, 헛쳤다. 명중 %d%%." % [attacker["name"], verb, target["name"], hit_chance]) _emit_log("%s %s %s, 헛쳤다. 명중 %d%%." % [attacker["name"], verb, target["name"], hit_chance])
_emit_combat_feedback(target, "헛침", "miss") _emit_combat_feedback(target, "헛침", "miss")
return {"hit": false, "defeated": false} return {
"attacker_id": str(attacker.get("id", "")),
"attacker_name": str(attacker.get("name", "")),
"target_id": str(target.get("id", "")),
"target_name": str(target.get("name", "")),
"is_counter": is_counter,
"hit": false,
"defeated": false,
"damage": 0,
"hit_chance": hit_chance,
"target_hp": int(target.get("hp", 0)),
"target_max_hp": int(target.get("max_hp", 0)),
"directional_label": "",
"directional_bonus": 0,
"effective_bonus": 0
}
var directional_info := get_directional_attack_info(attacker, target) var directional_info := get_directional_attack_info(attacker, target)
var damage := calculate_damage(attacker, target) var damage := calculate_damage(attacker, target)
target["hp"] = max(0, int(target["hp"]) - damage) target["hp"] = max(0, int(target["hp"]) - damage)
@@ -3799,8 +3836,38 @@ func _resolve_attack(attacker: Dictionary, target: Dictionary, is_counter := fal
_emit_log("%s 퇴각." % target["name"]) _emit_log("%s 퇴각." % target["name"])
_emit_combat_feedback(target, "퇴각", "defeat") _emit_combat_feedback(target, "퇴각", "defeat")
_handle_unit_defeated(target) _handle_unit_defeated(target)
return {"hit": true, "defeated": true} return {
return {"hit": true, "defeated": false} "attacker_id": str(attacker.get("id", "")),
"attacker_name": str(attacker.get("name", "")),
"target_id": str(target.get("id", "")),
"target_name": str(target.get("name", "")),
"is_counter": is_counter,
"hit": true,
"defeated": true,
"damage": damage,
"hit_chance": hit_chance,
"target_hp": int(target.get("hp", 0)),
"target_max_hp": int(target.get("max_hp", 0)),
"directional_label": str(directional_info.get("label", "")),
"directional_bonus": directional_bonus,
"effective_bonus": effective_bonus
}
return {
"attacker_id": str(attacker.get("id", "")),
"attacker_name": str(attacker.get("name", "")),
"target_id": str(target.get("id", "")),
"target_name": str(target.get("name", "")),
"is_counter": is_counter,
"hit": true,
"defeated": false,
"damage": damage,
"hit_chance": hit_chance,
"target_hp": int(target.get("hp", 0)),
"target_max_hp": int(target.get("max_hp", 0)),
"directional_label": str(directional_info.get("label", "")),
"directional_bonus": directional_bonus,
"effective_bonus": effective_bonus
}
func _resolve_skill_damage(caster: Dictionary, target: Dictionary, skill: Dictionary) -> bool: func _resolve_skill_damage(caster: Dictionary, target: Dictionary, skill: Dictionary) -> bool:

View File

@@ -100,6 +100,7 @@ const AUDIO_PREVIEW_LABEL := "확인음 · 발소리 · 타격음 · 퇴각음
const FLOATING_TEXT_LIFETIME := 1.0 const FLOATING_TEXT_LIFETIME := 1.0
const FLOATING_TEXT_RISE := 42.0 const FLOATING_TEXT_RISE := 42.0
const OBJECTIVE_NOTICE_DURATION := 2.6 const OBJECTIVE_NOTICE_DURATION := 2.6
const COMBAT_RESULT_NOTICE_DURATION := 2.8
const EDGE_SCROLL_MARGIN := 48.0 const EDGE_SCROLL_MARGIN := 48.0
const EDGE_SCROLL_OUTER_MARGIN := 28.0 const EDGE_SCROLL_OUTER_MARGIN := 28.0
const EDGE_SCROLL_MIN_SPEED := 80.0 const EDGE_SCROLL_MIN_SPEED := 80.0
@@ -194,6 +195,8 @@ const OPENING_STORY_PROGRESS_LEDGER_SIZE := Vector2(326, 48)
const OPENING_STORY_PROGRESS_SEAL_SIZE := Vector2(34, 34) const OPENING_STORY_PROGRESS_SEAL_SIZE := Vector2(34, 34)
const COMMAND_NOTICE_ICON_SIZE := Vector2(32, 32) const COMMAND_NOTICE_ICON_SIZE := Vector2(32, 32)
const COMMAND_NOTICE_MAX_ICONS := 5 const COMMAND_NOTICE_MAX_ICONS := 5
const COMBAT_RESULT_PANEL_POSITION := Vector2(838, 430)
const COMBAT_RESULT_PANEL_SIZE := Vector2(396, 128)
const CHAPTER_SEAL_BADGE_SIZE := Vector2(54, 54) const CHAPTER_SEAL_BADGE_SIZE := Vector2(54, 54)
const CHAPTER_SCENARIO_THUMBNAIL_SIZE := Vector2(72, 50) const CHAPTER_SCENARIO_THUMBNAIL_SIZE := Vector2(72, 50)
const CHAPTER_INFO_BADGE_SIZE := Vector2(86, 22) const CHAPTER_INFO_BADGE_SIZE := Vector2(86, 22)
@@ -479,6 +482,10 @@ var mission_detail_label: Label
var objective_notice_panel: PanelContainer var objective_notice_panel: PanelContainer
var objective_notice_icon_strip: HBoxContainer var objective_notice_icon_strip: HBoxContainer
var objective_notice_label: Label var objective_notice_label: Label
var combat_result_panel: PanelContainer
var combat_result_title_label: Label
var combat_result_primary_label: Label
var combat_result_detail_label: Label
var ui_root_control: Control var ui_root_control: Control
var screen_backdrop: ColorRect var screen_backdrop: ColorRect
var hud_unit_portrait_panel: PanelContainer var hud_unit_portrait_panel: PanelContainer
@@ -703,7 +710,9 @@ var audio_stream_cache := {}
var recent_sfx_keys: Array[String] = [] var recent_sfx_keys: Array[String] = []
var last_announced_battle_status := "" var last_announced_battle_status := ""
var floating_texts: Array[Dictionary] = [] var floating_texts: Array[Dictionary] = []
var pending_combat_result_report: Dictionary = {}
var objective_notice_timer := 0.0 var objective_notice_timer := 0.0
var combat_result_notice_timer := 0.0
var battle_start_notice_pending := false var battle_start_notice_pending := false
var unit_motion_by_unit: Dictionary = {} var unit_motion_by_unit: Dictionary = {}
var unit_action_motion_by_unit: Dictionary = {} var unit_action_motion_by_unit: Dictionary = {}
@@ -730,6 +739,7 @@ func _ready() -> void:
state.log_added.connect(_on_log_added) state.log_added.connect(_on_log_added)
state.dialogue_requested.connect(_on_dialogue_requested) state.dialogue_requested.connect(_on_dialogue_requested)
state.combat_feedback_requested.connect(_on_combat_feedback_requested) state.combat_feedback_requested.connect(_on_combat_feedback_requested)
state.combat_result_reported.connect(_on_combat_result_reported)
state.unit_motion_requested.connect(_on_unit_motion_requested) state.unit_motion_requested.connect(_on_unit_motion_requested)
state.unit_action_motion_requested.connect(_on_unit_action_motion_requested) state.unit_action_motion_requested.connect(_on_unit_action_motion_requested)
state.objective_updated.connect(_on_objective_updated) state.objective_updated.connect(_on_objective_updated)
@@ -3693,6 +3703,44 @@ func _create_hud() -> void:
objective_notice_column.add_child(objective_notice_label) objective_notice_column.add_child(objective_notice_label)
objective_notice_column.add_child(_make_scroll_rod(548, 3)) objective_notice_column.add_child(_make_scroll_rod(548, 3))
combat_result_panel = PanelContainer.new()
combat_result_panel.name = "CombatResultNotice"
combat_result_panel.visible = false
combat_result_panel.mouse_filter = Control.MOUSE_FILTER_PASS
combat_result_panel.position = COMBAT_RESULT_PANEL_POSITION
combat_result_panel.size = COMBAT_RESULT_PANEL_SIZE
combat_result_panel.custom_minimum_size = COMBAT_RESULT_PANEL_SIZE
combat_result_panel.z_as_relative = false
combat_result_panel.z_index = 73
_apply_panel_style(combat_result_panel, "hud_readable")
root.add_child(combat_result_panel)
var combat_result_column := VBoxContainer.new()
combat_result_column.add_theme_constant_override("separation", 5)
combat_result_panel.add_child(combat_result_column)
combat_result_title_label = Label.new()
combat_result_title_label.text = "전공 보고"
combat_result_title_label.custom_minimum_size = Vector2(360, 24)
combat_result_title_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
combat_result_title_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_LEFT
_apply_label_style(combat_result_title_label, UI_PARCHMENT_TEXT, 15)
combat_result_column.add_child(combat_result_title_label)
combat_result_primary_label = Label.new()
combat_result_primary_label.custom_minimum_size = Vector2(360, 30)
combat_result_primary_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
combat_result_primary_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
_apply_label_style(combat_result_primary_label, UI_RICE_PAPER_LIGHT, 15)
combat_result_column.add_child(combat_result_primary_label)
combat_result_detail_label = Label.new()
combat_result_detail_label.custom_minimum_size = Vector2(360, 44)
combat_result_detail_label.vertical_alignment = VERTICAL_ALIGNMENT_TOP
combat_result_detail_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
_apply_label_style(combat_result_detail_label, UI_PARCHMENT_TEXT, 13)
combat_result_column.add_child(combat_result_detail_label)
auto_end_turn_prompt_panel = PanelContainer.new() auto_end_turn_prompt_panel = PanelContainer.new()
auto_end_turn_prompt_panel.name = "AutoEndTurnPrompt" auto_end_turn_prompt_panel.name = "AutoEndTurnPrompt"
auto_end_turn_prompt_panel.visible = false auto_end_turn_prompt_panel.visible = false
@@ -6124,6 +6172,10 @@ func _process(delta: float) -> void:
objective_notice_timer = maxf(0.0, objective_notice_timer - delta) objective_notice_timer = maxf(0.0, objective_notice_timer - delta)
if objective_notice_timer <= 0.0 and objective_notice_panel != null: if objective_notice_timer <= 0.0 and objective_notice_panel != null:
objective_notice_panel.visible = false objective_notice_panel.visible = false
if combat_result_notice_timer > 0.0:
combat_result_notice_timer = maxf(0.0, combat_result_notice_timer - delta)
if combat_result_notice_timer <= 0.0 and combat_result_panel != null:
combat_result_panel.visible = false
if not floating_texts.is_empty(): if not floating_texts.is_empty():
var active_texts: Array[Dictionary] = [] var active_texts: Array[Dictionary] = []
@@ -12545,6 +12597,9 @@ func _on_dialogue_requested(lines: Array) -> void:
var normalized_lines := _normalized_dialogue_lines(lines) var normalized_lines := _normalized_dialogue_lines(lines)
if normalized_lines.is_empty(): if normalized_lines.is_empty():
return return
if combat_result_panel != null:
combat_result_panel.visible = false
combat_result_notice_timer = 0.0
dialogue_queue.append(normalized_lines) dialogue_queue.append(normalized_lines)
if _can_show_dialogue_now(): if _can_show_dialogue_now():
_show_next_dialogue() _show_next_dialogue()
@@ -12580,6 +12635,100 @@ func _on_combat_feedback_requested(unit_id: String, text: String, kind: String)
queue_redraw() queue_redraw()
func _on_combat_result_reported(report: Dictionary) -> void:
if report.is_empty():
return
if _is_dialogue_visible() or not dialogue_queue.is_empty():
pending_combat_result_report = report.duplicate(true)
return
_show_combat_result_report(report)
func _show_combat_result_report(report: Dictionary) -> void:
if combat_result_panel == null or report.is_empty():
return
var lines := _format_combat_result_report_lines(report)
if lines.is_empty():
return
combat_result_title_label.text = "전공 보고"
combat_result_primary_label.text = lines[0]
combat_result_primary_label.tooltip_text = _join_strings(lines, "\n")
combat_result_detail_label.text = _join_strings(lines.slice(1), "\n")
combat_result_detail_label.tooltip_text = combat_result_primary_label.tooltip_text
combat_result_panel.tooltip_text = combat_result_primary_label.tooltip_text
combat_result_panel.visible = true
combat_result_notice_timer = COMBAT_RESULT_NOTICE_DURATION
combat_result_panel.move_to_front()
func _show_pending_combat_result_if_possible() -> void:
if pending_combat_result_report.is_empty() or _is_dialogue_visible() or not dialogue_queue.is_empty():
return
var report := pending_combat_result_report.duplicate(true)
pending_combat_result_report.clear()
_show_combat_result_report(report)
func _format_combat_result_report_lines(report: Dictionary) -> Array[String]:
var lines: Array[String] = []
var entries: Array = report.get("entries", [])
for entry_value in entries:
if typeof(entry_value) != TYPE_DICTIONARY:
continue
var entry := entry_value as Dictionary
var line := _format_combat_result_entry(entry)
if not line.is_empty():
lines.append(line)
if int(report.get("attacker_merit", 0)) > 0:
lines.append("%s 공적 +%d" % [str(report.get("attacker_name", "아군")), int(report.get("attacker_merit", 0))])
if int(report.get("counter_merit", 0)) > 0:
var counter_name := _combat_result_counter_name(report)
lines.append("%s 공적 +%d" % [counter_name, int(report.get("counter_merit", 0))])
return lines
func _format_combat_result_entry(entry: Dictionary) -> String:
var attacker_name := str(entry.get("attacker_name", "부대"))
var target_name := str(entry.get("target_name", ""))
var verb := "반격" if bool(entry.get("is_counter", false)) else "공격"
if not bool(entry.get("hit", false)):
return "%s %s %s · 헛침 · 명중 %d%%" % [
attacker_name,
verb,
target_name,
int(entry.get("hit_chance", 0))
]
var parts: Array[String] = [
"%s %s %s" % [attacker_name, verb, target_name],
"%d 피해" % int(entry.get("damage", 0))
]
var directional_bonus := int(entry.get("directional_bonus", 0))
if directional_bonus > 0:
parts.append("%s +%d" % [str(entry.get("directional_label", "방향")), directional_bonus])
var effective_bonus := int(entry.get("effective_bonus", 0))
if effective_bonus > 0:
parts.append("특효 +%d" % effective_bonus)
if bool(entry.get("defeated", false)):
parts.append("퇴각")
else:
var target_hp := int(entry.get("target_hp", 0))
var target_max_hp := int(entry.get("target_max_hp", 0))
if target_max_hp > 0:
parts.append("잔여 %d/%d" % [target_hp, target_max_hp])
return _join_strings(parts, " · ")
func _combat_result_counter_name(report: Dictionary) -> String:
var entries: Array = report.get("entries", [])
for entry_value in entries:
if typeof(entry_value) != TYPE_DICTIONARY:
continue
var entry := entry_value as Dictionary
if bool(entry.get("is_counter", false)):
return str(entry.get("attacker_name", report.get("target_name", "반격 부대")))
return str(report.get("target_name", "반격 부대"))
func _combat_feedback_sfx_key(text: String, kind: String) -> String: func _combat_feedback_sfx_key(text: String, kind: String) -> String:
if kind == "reinforcement" or text == "증원": if kind == "reinforcement" or text == "증원":
return "footstep" return "footstep"
@@ -13093,6 +13242,7 @@ func _advance_dialogue() -> void:
_show_next_dialogue() _show_next_dialogue()
else: else:
_show_battle_start_notice_if_ready() _show_battle_start_notice_if_ready()
_show_pending_combat_result_if_possible()
_update_hud() _update_hud()
queue_redraw() queue_redraw()

View File

@@ -123,6 +123,10 @@ func _capture_viewport_set(spec: Dictionary, output_dir: String, captures: Array
await _settle() await _settle()
await _capture_frame(scene, output_dir, label, "12_post_move_item_picker", captures, failures) await _capture_frame(scene, output_dir, label, "12_post_move_item_picker", captures, failures)
_prepare_combat_result_frame(scene)
await _settle()
await _capture_frame(scene, output_dir, label, "13_battle_combat_result", captures, failures)
scene.queue_free() scene.queue_free()
await _settle() await _settle()
@@ -194,12 +198,48 @@ func _prepare_post_move_action_frame(scene, picker_mode: String) -> void:
scene.queue_redraw() scene.queue_redraw()
func _prepare_combat_result_frame(scene) -> void:
scene._hide_command_hint()
scene._hide_post_move_menu()
scene._hide_post_move_picker()
scene._hide_tactic_menu()
scene._hide_item_menu()
scene._hide_equip_menu()
scene._clear_pending_move_state()
scene.selected_skill_id = ""
scene.selected_item_id = ""
scene.basic_attack_targeting = false
var cao_cao: Dictionary = scene.state.get_unit("cao_cao")
var enemy: Dictionary = scene.state.get_unit("yellow_turban_1")
if cao_cao.is_empty() or enemy.is_empty():
return
cao_cao["pos"] = Vector2i(2, 3)
cao_cao["moved"] = false
cao_cao["acted"] = false
cao_cao["atk"] = maxi(48, int(cao_cao.get("atk", 0)))
cao_cao["agi"] = 999
enemy["pos"] = Vector2i(3, 3)
enemy["alive"] = true
enemy["hp"] = 90
enemy["max_hp"] = 90
enemy["def"] = 1
scene.state.rng.seed = 1
scene.state.select_unit("cao_cao")
scene._refresh_ranges()
scene._focus_board_on_cell(Vector2i(3, 3))
scene.state.try_attack_selected("yellow_turban_1")
scene._update_hud()
scene.queue_redraw()
func _screen_for_cell(scene, cell: Vector2i) -> Vector2: func _screen_for_cell(scene, cell: Vector2i) -> Vector2:
var rect: Rect2 = scene._rect_for_cell(cell) var rect: Rect2 = scene._rect_for_cell(cell)
return rect.position + rect.size * 0.5 return rect.position + rect.size * 0.5
func _capture_frame(_scene, output_dir: String, viewport_label: String, frame_label: String, captures: Array[Dictionary], failures: Array[String]) -> void: func _capture_frame(_scene, output_dir: String, viewport_label: String, frame_label: String, captures: Array[Dictionary], failures: Array[String]) -> void:
Input.warp_mouse(Vector2(12, 12))
await _settle()
var texture := get_root().get_texture() var texture := get_root().get_texture()
if texture == null: if texture == null:
failures.append("viewport texture missing for %s/%s" % [viewport_label, frame_label]) failures.append("viewport texture missing for %s/%s" % [viewport_label, frame_label])

View File

@@ -619,6 +619,8 @@ func _check_scene_post_move_attack_targeting(failures: Array[String]) -> void:
if unit_id == "cao_cao" and kind == "progress": if unit_id == "cao_cao" and kind == "progress":
progress_feedback.append(text) progress_feedback.append(text)
) )
if not scene.state.combat_result_reported.is_connected(scene._on_combat_result_reported):
scene.state.combat_result_reported.connect(scene._on_combat_result_reported)
scene._on_post_move_attack_pressed() scene._on_post_move_attack_pressed()
var enemy_hp_before := int(enemy.get("hp", 0)) var enemy_hp_before := int(enemy.get("hp", 0))
scene._handle_board_click(_screen_for_cell(Vector2i(3, 3))) scene._handle_board_click(_screen_for_cell(Vector2i(3, 3)))
@@ -636,6 +638,12 @@ func _check_scene_post_move_attack_targeting(failures: Array[String]) -> void:
failures.append("attack click should commit deferred movement event before resolving attack") failures.append("attack click should commit deferred movement event before resolving attack")
if not _feedback_contains(progress_feedback, "공적 +"): if not _feedback_contains(progress_feedback, "공적 +"):
failures.append("attack click should show visible merit feedback over the unit: %s" % str(progress_feedback)) failures.append("attack click should show visible merit feedback over the unit: %s" % str(progress_feedback))
if scene.combat_result_panel == null or not scene.combat_result_panel.visible:
failures.append("attack click should show a compact combat result report")
else:
var result_text := "%s\n%s" % [scene.combat_result_primary_label.text, scene.combat_result_detail_label.text]
if not result_text.contains("공격") or not result_text.contains("피해") or not result_text.contains("공적 +"):
failures.append("combat result report should summarize damage and merit: %s" % result_text)
scene.free() scene.free()

View File

@@ -3881,6 +3881,35 @@ func _check_ancient_ui_theme(failures: Array[String]) -> void:
_check_panel_style_frame(failures, scene.cell_info_panel, "HUD terrain info panel", Color(0.42, 0.35, 0.24, 0.98), 2) _check_panel_style_frame(failures, scene.cell_info_panel, "HUD terrain info panel", Color(0.42, 0.35, 0.24, 0.98), 2)
_check_panel_style_fill(failures, scene.forecast_panel, "HUD forecast info panel", Color(0.060, 0.056, 0.048, 0.982)) _check_panel_style_fill(failures, scene.forecast_panel, "HUD forecast info panel", Color(0.060, 0.056, 0.048, 0.982))
_check_panel_style_frame(failures, scene.forecast_panel, "HUD forecast info panel", Color(0.42, 0.35, 0.24, 0.98), 2) _check_panel_style_frame(failures, scene.forecast_panel, "HUD forecast info panel", Color(0.42, 0.35, 0.24, 0.98), 2)
_check_panel_style_fill(failures, scene.combat_result_panel, "combat result readable frame", Color(0.060, 0.056, 0.048, 0.982))
_check_panel_style_frame(failures, scene.combat_result_panel, "combat result readable frame", Color(0.42, 0.35, 0.24, 0.98), 2)
if scene.combat_result_panel == null or scene.combat_result_panel.custom_minimum_size != BattleSceneScript.COMBAT_RESULT_PANEL_SIZE:
failures.append("combat result report should reserve a stable QHD layout size")
scene._on_combat_result_reported({
"attacker_name": "조조",
"target_name": "황건병",
"attacker_merit": 10,
"counter_merit": 0,
"entries": [{
"attacker_name": "조조",
"target_name": "황건병",
"is_counter": false,
"hit": true,
"defeated": false,
"damage": 18,
"hit_chance": 92,
"target_hp": 12,
"target_max_hp": 30,
"directional_bonus": 2,
"directional_label": "후방 공격",
"effective_bonus": 0
}]
})
if scene.combat_result_panel == null or not scene.combat_result_panel.visible:
failures.append("combat result report should appear after a battle result signal")
elif not scene.combat_result_primary_label.text.contains("피해") or not scene.combat_result_detail_label.text.contains("공적 +10"):
failures.append("combat result report should show damage and merit in readable Korean: %s / %s" % [scene.combat_result_primary_label.text, scene.combat_result_detail_label.text])
scene.combat_result_panel.visible = false
_check_panel_uses_flat_readable_backing(failures, scene.title_load_panel, "title load readable panel") _check_panel_uses_flat_readable_backing(failures, scene.title_load_panel, "title load readable panel")
_check_panel_uses_flat_readable_backing(failures, scene.title_settings_panel, "title settings readable panel") _check_panel_uses_flat_readable_backing(failures, scene.title_settings_panel, "title settings readable panel")
_check_panel_uses_flat_readable_backing(failures, _find_descendant_by_name(scene.ui_root_control, "SettingsPanel") as PanelContainer, "battle settings readable panel") _check_panel_uses_flat_readable_backing(failures, _find_descendant_by_name(scene.ui_root_control, "SettingsPanel") as PanelContainer, "battle settings readable panel")