Refine battle flow prompts and opening

This commit is contained in:
2026-06-19 22:47:32 +09:00
parent 4b22c13ca9
commit 3b91344b6f
5 changed files with 396 additions and 60 deletions

View File

@@ -638,6 +638,13 @@ func _check_opening_battle_event_dialogue_structure(failures: Array[String]) ->
continue
if not _event_has_dialogue_action(event):
failures.append("opening battle event should include contextual dialogue: %s" % event_id)
var opening_event := _event_by_id(state, "opening_dialogue")
if not opening_event.is_empty():
var opening_text := _event_dialogue_text(opening_event)
if not opening_text.contains("젊은 조조") or not opening_text.contains("하후돈") or not opening_text.contains("첫 군령"):
failures.append("opening battle should first explain Cao Cao, Xiahou Dun, and why the first battle begins")
if opening_text.find("젊은 조조") > opening_text.find("황건의 깃발"):
failures.append("opening battle prologue should come before the tactical battlefield order")
var boss_event := _event_by_id(state, "zhang_mancheng_falls")
if not boss_event.is_empty():
var when: Dictionary = boss_event.get("when", {})
@@ -1520,6 +1527,21 @@ func _event_has_dialogue_action(event: Dictionary) -> bool:
return false
func _event_dialogue_text(event: Dictionary) -> String:
var parts: Array[String] = []
var actions: Array = event.get("actions", [])
for action in actions:
if typeof(action) != TYPE_DICTIONARY or str(action.get("type", "")) != "dialogue":
continue
var lines: Array = (action as Dictionary).get("lines", [])
for line in lines:
if typeof(line) == TYPE_DICTIONARY:
parts.append(str((line as Dictionary).get("text", "")))
elif typeof(line) == TYPE_STRING:
parts.append(str(line))
return "\n".join(parts)
func _event_has_set_objective_action(event: Dictionary) -> bool:
var actions: Array = event.get("actions", [])
for action in actions:

View File

@@ -7,6 +7,7 @@ const BattleSceneScript := preload("res://scripts/scenes/battle_scene.gd")
func _init() -> void:
var failures: Array[String] = []
_check_deferred_move_events(failures)
_check_auto_end_turn_prompt_flow(failures)
_check_scene_post_move_menu_flow(failures)
_check_scene_post_move_edge_positioning(failures)
_check_scene_post_move_scrolled_view_positioning(failures)
@@ -79,6 +80,63 @@ func _check_deferred_move_events(failures: Array[String]) -> void:
failures.append("pending move commit should fire deferred movement event")
func _check_auto_end_turn_prompt_flow(failures: Array[String]) -> void:
var scene = BattleSceneScript.new()
scene._create_hud()
if not scene.state.load_battle("res://data/scenarios/001_yellow_turbans.json"):
failures.append("could not load opening battle for auto end turn prompt")
scene.free()
return
scene.battle_started = true
scene.campaign_complete_screen = false
scene.briefing_panel.visible = false
scene.result_panel.visible = false
scene._clear_pending_move_state()
scene.state.selected_unit_id = ""
if scene.auto_end_turn_prompt_panel == null:
failures.append("auto end turn prompt should be created with the HUD")
scene.free()
return
for unit in scene.state.units:
if str(unit.get("team", "")) == BattleStateScript.TEAM_PLAYER and bool(unit.get("controllable", true)):
unit["acted"] = false
scene._update_hud()
if scene.auto_end_turn_prompt_panel.visible:
failures.append("auto end turn prompt should stay hidden while a controllable ally can still act")
for unit in scene.state.units:
if str(unit.get("team", "")) == BattleStateScript.TEAM_PLAYER and bool(unit.get("alive", true)) and bool(unit.get("deployed", true)) and bool(unit.get("controllable", true)):
unit["acted"] = true
unit["moved"] = true
scene._update_hud()
if not scene.auto_end_turn_prompt_panel.visible:
failures.append("auto end turn prompt should appear once every controllable ally has acted")
if not scene._is_input_locked():
failures.append("auto end turn prompt should lock board input while visible")
if not scene._handle_cancel_input():
failures.append("right-click cancel should dismiss the auto end turn prompt")
if scene.auto_end_turn_prompt_panel.visible:
failures.append("auto end turn prompt should hide after cancel")
scene._update_hud()
if scene.auto_end_turn_prompt_panel.visible:
failures.append("declined auto end turn prompt should not reopen in the same turn")
scene.auto_end_turn_prompt_declined_turn = -1
scene._update_hud()
if not scene.auto_end_turn_prompt_panel.visible:
failures.append("auto end turn prompt should reopen after the declined-turn guard is cleared")
var turn_before: int = scene.state.turn_number
scene._on_auto_end_turn_yes_pressed()
if scene.auto_end_turn_prompt_panel.visible:
failures.append("auto end turn prompt should hide after confirming turn end")
if scene.state.turn_number <= turn_before and scene.state.battle_status == BattleStateScript.STATUS_ACTIVE:
failures.append("confirming auto end turn should advance through the enemy phase")
scene.free()
func _check_scene_post_move_menu_flow(failures: Array[String]) -> void:
var scene = BattleSceneScript.new()
scene._create_hud()
@@ -710,8 +768,9 @@ func _check_scene_auto_attack_safe_origin_and_counter_preview(failures: Array[St
risky_scene.hover_cell = Vector2i(5, 3)
risky_scene._update_forecast()
var risky_forecast: String = risky_scene.forecast_label.text
if not risky_forecast.contains("행공") or not risky_forecast.contains("반격"):
failures.append("risky auto attack forecast should show counter risk: %s" % risky_forecast)
var risky_detail := str(risky_scene.forecast_label.tooltip_text)
if not risky_forecast.contains("행공") or not risky_detail.contains("반격"):
failures.append("risky auto attack forecast should summarize on the label and keep counter risk in the tooltip: %s / %s" % [risky_forecast, risky_detail])
risky_enemy["controllable"] = false
var escort_preview := risky_scene._auto_attack_preview_for_origin(risky_attacker, risky_enemy, Vector2i(4, 3))
if bool(escort_preview.get("counter_in_range", false)):

View File

@@ -3222,16 +3222,14 @@ func _check_ancient_ui_theme(failures: Array[String]) -> void:
_check_bamboo_gutter_row(failures, scene.mission_detail_panel, "mission detail bamboo gutters")
_check_edict_marker_stack(failures, scene.briefing_objective_panel, "briefing objective edict markers")
_check_edict_marker_stack(failures, scene.mission_detail_panel, "mission detail edict markers")
var cell_info_panel: PanelContainer = null
if scene.cell_info_label != null:
cell_info_panel = scene.cell_info_label.get_parent() as PanelContainer
_check_panel_style_fill(failures, cell_info_panel, "HUD terrain info panel", Color(0.075, 0.050, 0.034, 0.88))
_check_panel_style_frame(failures, cell_info_panel, "HUD terrain info panel", Color(0.31, 0.22, 0.12, 0.96), 3)
var forecast_panel: PanelContainer = null
if scene.forecast_label != null:
forecast_panel = scene.forecast_label.get_parent() as PanelContainer
_check_panel_style_fill(failures, forecast_panel, "HUD forecast info panel", Color(0.075, 0.050, 0.034, 0.88))
_check_panel_style_frame(failures, forecast_panel, "HUD forecast info panel", Color(0.31, 0.22, 0.12, 0.96), 3)
_check_panel_style_fill(failures, scene.cell_info_panel, "HUD terrain info panel", Color(0.075, 0.050, 0.034, 0.88))
_check_panel_style_frame(failures, scene.cell_info_panel, "HUD terrain info panel", Color(0.31, 0.22, 0.12, 0.96), 3)
_check_panel_style_fill(failures, scene.forecast_panel, "HUD forecast info panel", Color(0.075, 0.050, 0.034, 0.88))
_check_panel_style_frame(failures, scene.forecast_panel, "HUD forecast info panel", Color(0.31, 0.22, 0.12, 0.96), 3)
if scene.cell_info_icon == null:
failures.append("HUD terrain info panel should expose an icon chip")
if scene.forecast_icon == null:
failures.append("HUD forecast info panel should expose an icon chip")
_check_hanging_tassel(failures, scene.dialogue_left_tassel, "dialogue left tassel")
_check_hanging_tassel(failures, scene.dialogue_right_tassel, "dialogue right tassel")
_check_dialogue_column_budget(failures, scene)
@@ -3258,10 +3256,16 @@ func _check_ancient_ui_theme(failures: Array[String]) -> void:
if right_lines.is_empty():
failures.append("dialogue normalization should keep right-side lines")
else:
var portrait_index_before: int = scene.dialogue_row.get_children().find(scene.dialogue_portrait_panel)
scene.active_dialogue_lines = right_lines
scene.active_dialogue_index = 0
scene._render_dialogue_line()
_check_right_side_dialogue_layout(failures, scene)
var portrait_index_after: int = scene.dialogue_row.get_children().find(scene.dialogue_portrait_panel)
if portrait_index_after != portrait_index_before:
failures.append("right-side dialogue should not move the portrait panel between lines")
if scene.dialogue_continue_button == null or scene.dialogue_continue_button.text != "다음":
failures.append("dialogue continue button should stay stable even on the final line")
scene._hide_dialogue_panel()
if scene._ui_font(false) == null:
failures.append("ancient UI regular font should load from project assets")
@@ -3426,10 +3430,10 @@ func _check_right_side_dialogue_layout(failures: Array[String], scene) -> void:
return
if scene.dialogue_row.get_child(0) != scene.dialogue_left_tassel:
failures.append("right-side dialogue should keep the left tassel at the outer edge")
if scene.dialogue_row.get_child(1) != scene.dialogue_column:
failures.append("right-side dialogue should place speech scroll before the portrait")
if scene.dialogue_row.get_child(2) != scene.dialogue_portrait_panel:
failures.append("right-side dialogue should place portrait near the right tassel")
if scene.dialogue_row.get_child(1) != scene.dialogue_portrait_panel:
failures.append("right-side dialogue should keep the portrait fixed beside the left tassel")
if scene.dialogue_row.get_child(2) != scene.dialogue_column:
failures.append("right-side dialogue should keep the speech scroll in its fixed column")
if scene.dialogue_row.get_child(3) != scene.dialogue_right_tassel:
failures.append("right-side dialogue should keep the right tassel at the outer edge")
if scene.dialogue_speaker_label == null or scene.dialogue_speaker_label.horizontal_alignment != HORIZONTAL_ALIGNMENT_RIGHT:
@@ -3895,15 +3899,17 @@ func _check_terrain_and_unit_presentation(failures: Array[String]) -> void:
failures.append("idle forecast panel should stay hidden instead of showing empty tactical text")
scene.hover_cell = Vector2i(20, 1)
scene._update_cell_info()
if scene.cell_info_label == null or not scene.cell_info_label.text.contains("목표: 성채 장악") or not scene.cell_info_label.text.contains("회복 +8"):
if scene.cell_info_label == null or not scene.cell_info_label.text.contains("목표 성채 장악") or not scene.cell_info_label.text.contains("회복 +8"):
failures.append("castle objective cell info should expose capture label and healing: %s" % scene.cell_info_label.text)
if scene.cell_info_label != null and not scene.cell_info_label.tooltip_text.contains("21,2"):
failures.append("castle objective cell info should keep coordinates in the hover tooltip: %s" % scene.cell_info_label.tooltip_text)
if scene.cell_info_panel == null or not scene.cell_info_panel.visible:
failures.append("cell info panel should be visible for a hovered battlefield cell")
if scene.cell_info_label.text.contains("20,2"):
failures.append("visible cell info should stay compact and leave coordinates to tooltip/badge: %s" % scene.cell_info_label.text)
scene.hover_cell = Vector2i(4, 2)
scene._update_cell_info()
if scene.cell_info_label == null or not scene.cell_info_label.text.contains("표식: 북숲 보급고"):
if scene.cell_info_label == null or not scene.cell_info_label.text.contains("표식 북숲 보급고"):
failures.append("side event marker cell info should expose the supply label: %s" % scene.cell_info_label.text)
scene.hover_cell = Vector2i(-1, -1)
scene._update_cell_info()