Add post-move attack targeting hints
This commit is contained in:
@@ -61,6 +61,8 @@ const HUD_UNIT_PORTRAIT_SIZE := Vector2(92, 104)
|
|||||||
const HUD_UNIT_PORTRAIT_STACK_SIZE := Vector2(88, 100)
|
const HUD_UNIT_PORTRAIT_STACK_SIZE := Vector2(88, 100)
|
||||||
const POST_MOVE_MENU_SIZE := Vector2(228, 120)
|
const POST_MOVE_MENU_SIZE := Vector2(228, 120)
|
||||||
const POST_MOVE_MENU_OFFSET := Vector2(20, -34)
|
const POST_MOVE_MENU_OFFSET := Vector2(20, -34)
|
||||||
|
const TARGETING_HINT_PANEL_SIZE := Vector2(240, 94)
|
||||||
|
const TARGETING_HINT_PANEL_OFFSET := Vector2(18, 88)
|
||||||
|
|
||||||
var state: BattleState = BattleStateScript.new()
|
var state: BattleState = BattleStateScript.new()
|
||||||
var campaign_state: CampaignState = CampaignStateScript.new()
|
var campaign_state: CampaignState = CampaignStateScript.new()
|
||||||
@@ -171,6 +173,11 @@ var post_move_tactic_button: Button
|
|||||||
var post_move_item_button: Button
|
var post_move_item_button: Button
|
||||||
var post_move_wait_button: Button
|
var post_move_wait_button: Button
|
||||||
var post_move_cancel_button: Button
|
var post_move_cancel_button: Button
|
||||||
|
var targeting_hint_panel: PanelContainer
|
||||||
|
var targeting_hint_title_label: Label
|
||||||
|
var targeting_hint_detail_label: Label
|
||||||
|
var targeting_hint_back_button: Button
|
||||||
|
var targeting_hint_cancel_move_button: Button
|
||||||
var threat_button: Button
|
var threat_button: Button
|
||||||
var restart_button: Button
|
var restart_button: Button
|
||||||
var new_campaign_button: Button
|
var new_campaign_button: Button
|
||||||
@@ -243,6 +250,7 @@ func _draw() -> void:
|
|||||||
_draw_map()
|
_draw_map()
|
||||||
_draw_overlays()
|
_draw_overlays()
|
||||||
_draw_units()
|
_draw_units()
|
||||||
|
_draw_attack_target_markers()
|
||||||
_draw_attack_effects()
|
_draw_attack_effects()
|
||||||
_draw_target_preview_badge()
|
_draw_target_preview_badge()
|
||||||
_draw_floating_texts()
|
_draw_floating_texts()
|
||||||
@@ -496,6 +504,43 @@ func _create_hud() -> void:
|
|||||||
post_move_cancel_button.pressed.connect(_on_post_move_cancel_pressed)
|
post_move_cancel_button.pressed.connect(_on_post_move_cancel_pressed)
|
||||||
post_move_column.add_child(post_move_cancel_button)
|
post_move_column.add_child(post_move_cancel_button)
|
||||||
|
|
||||||
|
targeting_hint_panel = PanelContainer.new()
|
||||||
|
targeting_hint_panel.visible = false
|
||||||
|
targeting_hint_panel.mouse_filter = Control.MOUSE_FILTER_STOP
|
||||||
|
targeting_hint_panel.custom_minimum_size = TARGETING_HINT_PANEL_SIZE
|
||||||
|
targeting_hint_panel.size = TARGETING_HINT_PANEL_SIZE
|
||||||
|
root.add_child(targeting_hint_panel)
|
||||||
|
|
||||||
|
var targeting_column := VBoxContainer.new()
|
||||||
|
targeting_column.add_theme_constant_override("separation", 4)
|
||||||
|
targeting_hint_panel.add_child(targeting_column)
|
||||||
|
|
||||||
|
targeting_hint_title_label = Label.new()
|
||||||
|
targeting_hint_title_label.text = "Choose Target"
|
||||||
|
targeting_hint_title_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||||
|
targeting_column.add_child(targeting_hint_title_label)
|
||||||
|
|
||||||
|
targeting_hint_detail_label = Label.new()
|
||||||
|
targeting_hint_detail_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||||
|
targeting_hint_detail_label.custom_minimum_size = Vector2(220, 28)
|
||||||
|
targeting_column.add_child(targeting_hint_detail_label)
|
||||||
|
|
||||||
|
var targeting_button_row := HBoxContainer.new()
|
||||||
|
targeting_button_row.add_theme_constant_override("separation", 6)
|
||||||
|
targeting_column.add_child(targeting_button_row)
|
||||||
|
|
||||||
|
targeting_hint_back_button = Button.new()
|
||||||
|
targeting_hint_back_button.text = "Back"
|
||||||
|
targeting_hint_back_button.tooltip_text = "Return to the action menu."
|
||||||
|
targeting_hint_back_button.pressed.connect(_on_targeting_back_pressed)
|
||||||
|
targeting_button_row.add_child(targeting_hint_back_button)
|
||||||
|
|
||||||
|
targeting_hint_cancel_move_button = Button.new()
|
||||||
|
targeting_hint_cancel_move_button.text = "Cancel Move"
|
||||||
|
targeting_hint_cancel_move_button.tooltip_text = "Return to the starting cell."
|
||||||
|
targeting_hint_cancel_move_button.pressed.connect(_on_targeting_cancel_move_pressed)
|
||||||
|
targeting_button_row.add_child(targeting_hint_cancel_move_button)
|
||||||
|
|
||||||
log_box = RichTextLabel.new()
|
log_box = RichTextLabel.new()
|
||||||
log_box.custom_minimum_size = Vector2(420, 70)
|
log_box.custom_minimum_size = Vector2(420, 70)
|
||||||
log_box.fit_content = false
|
log_box.fit_content = false
|
||||||
@@ -1101,6 +1146,7 @@ func _clear_pending_move_state(hide_menu := true) -> void:
|
|||||||
basic_attack_targeting = false
|
basic_attack_targeting = false
|
||||||
if hide_menu:
|
if hide_menu:
|
||||||
_hide_post_move_menu()
|
_hide_post_move_menu()
|
||||||
|
_hide_targeting_hint_panel()
|
||||||
|
|
||||||
|
|
||||||
func _commit_pending_move_before_action() -> bool:
|
func _commit_pending_move_before_action() -> bool:
|
||||||
@@ -1161,6 +1207,9 @@ func _handle_cancel_input() -> bool:
|
|||||||
selected_skill_id = ""
|
selected_skill_id = ""
|
||||||
selected_item_id = ""
|
selected_item_id = ""
|
||||||
basic_attack_targeting = false
|
basic_attack_targeting = false
|
||||||
|
_hide_targeting_hint_panel()
|
||||||
|
if _has_pending_move():
|
||||||
|
_show_post_move_menu()
|
||||||
_refresh_ranges()
|
_refresh_ranges()
|
||||||
_update_hud()
|
_update_hud()
|
||||||
queue_redraw()
|
queue_redraw()
|
||||||
@@ -1608,6 +1657,63 @@ func _draw_units() -> void:
|
|||||||
draw_arc(center, 29, 0.0, TAU, 48, Color(1.0, 0.92, 0.25), 3.0)
|
draw_arc(center, 29, 0.0, TAU, 48, Color(1.0, 0.92, 0.25), 3.0)
|
||||||
|
|
||||||
|
|
||||||
|
func _draw_attack_target_markers() -> void:
|
||||||
|
if not basic_attack_targeting:
|
||||||
|
return
|
||||||
|
var font := ThemeDB.fallback_font
|
||||||
|
for marker in _attack_target_marker_entries():
|
||||||
|
var cell: Vector2i = marker.get("cell", Vector2i(-1, -1))
|
||||||
|
if not state.is_inside(cell):
|
||||||
|
continue
|
||||||
|
var rect := _rect_for_cell(cell)
|
||||||
|
var marker_rect := Rect2(rect.position + Vector2(7.0, TILE_SIZE - 24.0), Vector2(TILE_SIZE - 14.0, 18.0))
|
||||||
|
var color := _target_preview_badge_color(str(marker.get("kind", "damage")))
|
||||||
|
draw_rect(rect.grow(-3.0), Color(1.0, 0.22, 0.16, 0.18))
|
||||||
|
draw_rect(rect.grow(-6.0), color, false, 2.2)
|
||||||
|
draw_rect(marker_rect, Color(0.025, 0.028, 0.034, 0.88))
|
||||||
|
draw_rect(marker_rect, color, false, 1.4)
|
||||||
|
draw_string(font, marker_rect.position + Vector2(0, 13), str(marker.get("text", "TARGET")), HORIZONTAL_ALIGNMENT_CENTER, marker_rect.size.x, 12, color)
|
||||||
|
|
||||||
|
|
||||||
|
func _attack_target_marker_entries() -> Array[Dictionary]:
|
||||||
|
var result: Array[Dictionary] = []
|
||||||
|
if not basic_attack_targeting:
|
||||||
|
return result
|
||||||
|
var selected := state.get_selected_unit()
|
||||||
|
if selected.is_empty():
|
||||||
|
return result
|
||||||
|
for cell in attack_cells:
|
||||||
|
var target := state.get_unit_at(cell)
|
||||||
|
if target.is_empty() or target.get("team", "") == selected.get("team", ""):
|
||||||
|
continue
|
||||||
|
var preview := state.get_damage_preview(str(selected.get("id", "")), str(target.get("id", "")))
|
||||||
|
if preview.is_empty() or not bool(preview.get("in_range", false)) or bool(preview.get("attack_locked", false)):
|
||||||
|
continue
|
||||||
|
var hit_chance := int(preview.get("hit_chance", 100))
|
||||||
|
var result_text := "KO" if bool(preview.get("would_defeat", false)) else "-%d" % int(preview.get("damage", 0))
|
||||||
|
result.append({
|
||||||
|
"cell": cell,
|
||||||
|
"target_id": str(target.get("id", "")),
|
||||||
|
"text": "%s %d%%" % [result_text, hit_chance],
|
||||||
|
"kind": "damage"
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
func _has_basic_attack_target(unit_id: String) -> bool:
|
||||||
|
var unit := state.get_unit(unit_id)
|
||||||
|
if unit.is_empty():
|
||||||
|
return false
|
||||||
|
for cell in state.get_attack_cells(unit_id):
|
||||||
|
var target := state.get_unit_at(cell)
|
||||||
|
if target.is_empty() or target.get("team", "") == unit.get("team", ""):
|
||||||
|
continue
|
||||||
|
var preview := state.get_damage_preview(unit_id, str(target.get("id", "")))
|
||||||
|
if not preview.is_empty() and bool(preview.get("in_range", false)) and not bool(preview.get("attack_locked", false)):
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
|
||||||
|
|
||||||
func _fit_texture_rect(texture: Texture2D, target: Rect2) -> Rect2:
|
func _fit_texture_rect(texture: Texture2D, target: Rect2) -> Rect2:
|
||||||
var texture_size := texture.get_size()
|
var texture_size := texture.get_size()
|
||||||
if texture_size.x <= 0.0 or texture_size.y <= 0.0:
|
if texture_size.x <= 0.0 or texture_size.y <= 0.0:
|
||||||
@@ -2291,6 +2397,7 @@ func _update_hud() -> void:
|
|||||||
restart_button.disabled = campaign_complete_screen
|
restart_button.disabled = campaign_complete_screen
|
||||||
_update_threat_button()
|
_update_threat_button()
|
||||||
_update_post_move_menu()
|
_update_post_move_menu()
|
||||||
|
_update_targeting_hint_panel()
|
||||||
|
|
||||||
|
|
||||||
func _format_objective_hud_text() -> String:
|
func _format_objective_hud_text() -> String:
|
||||||
@@ -5139,7 +5246,7 @@ func _update_post_move_menu() -> void:
|
|||||||
if post_move_title_label != null:
|
if post_move_title_label != null:
|
||||||
post_move_title_label.text = "%s: choose action" % str(selected.get("name", "Unit"))
|
post_move_title_label.text = "%s: choose action" % str(selected.get("name", "Unit"))
|
||||||
if post_move_attack_button != null:
|
if post_move_attack_button != null:
|
||||||
var attack_blocked := state.get_attack_cells(pending_move_unit_id).is_empty()
|
var attack_blocked := not _has_basic_attack_target(pending_move_unit_id)
|
||||||
post_move_attack_button.disabled = attack_blocked
|
post_move_attack_button.disabled = attack_blocked
|
||||||
post_move_attack_button.tooltip_text = "No enemy is in range after this move." if attack_blocked else "Choose an enemy target from this unit's current position."
|
post_move_attack_button.tooltip_text = "No enemy is in range after this move." if attack_blocked else "Choose an enemy target from this unit's current position."
|
||||||
if post_move_tactic_button != null:
|
if post_move_tactic_button != null:
|
||||||
@@ -5177,7 +5284,7 @@ func _position_post_move_menu() -> void:
|
|||||||
func _on_post_move_attack_pressed() -> void:
|
func _on_post_move_attack_pressed() -> void:
|
||||||
if not _has_pending_move():
|
if not _has_pending_move():
|
||||||
return
|
return
|
||||||
if state.get_attack_cells(pending_move_unit_id).is_empty():
|
if not _has_basic_attack_target(pending_move_unit_id):
|
||||||
_play_ui_cancel()
|
_play_ui_cancel()
|
||||||
return
|
return
|
||||||
_play_ui_click()
|
_play_ui_click()
|
||||||
@@ -5224,6 +5331,113 @@ func _on_post_move_cancel_pressed() -> void:
|
|||||||
_cancel_pending_move()
|
_cancel_pending_move()
|
||||||
|
|
||||||
|
|
||||||
|
func _is_targeting_mode() -> bool:
|
||||||
|
return basic_attack_targeting or not selected_skill_id.is_empty() or not selected_item_id.is_empty()
|
||||||
|
|
||||||
|
|
||||||
|
func _show_targeting_hint_panel() -> void:
|
||||||
|
if targeting_hint_panel == null:
|
||||||
|
return
|
||||||
|
_update_targeting_hint_panel()
|
||||||
|
if targeting_hint_panel != null and _is_targeting_mode():
|
||||||
|
targeting_hint_panel.visible = true
|
||||||
|
targeting_hint_panel.move_to_front()
|
||||||
|
|
||||||
|
|
||||||
|
func _hide_targeting_hint_panel() -> void:
|
||||||
|
if targeting_hint_panel == null:
|
||||||
|
return
|
||||||
|
targeting_hint_panel.visible = false
|
||||||
|
|
||||||
|
|
||||||
|
func _update_targeting_hint_panel() -> void:
|
||||||
|
if targeting_hint_panel == null:
|
||||||
|
return
|
||||||
|
if not _is_targeting_mode():
|
||||||
|
_hide_targeting_hint_panel()
|
||||||
|
return
|
||||||
|
var selected := state.get_selected_unit()
|
||||||
|
if selected.is_empty() or bool(selected.get("acted", false)) or _is_input_locked():
|
||||||
|
_hide_targeting_hint_panel()
|
||||||
|
return
|
||||||
|
_position_targeting_hint_panel(selected)
|
||||||
|
if targeting_hint_title_label != null:
|
||||||
|
targeting_hint_title_label.text = _targeting_hint_title_text()
|
||||||
|
if targeting_hint_detail_label != null:
|
||||||
|
targeting_hint_detail_label.text = _targeting_hint_detail_text()
|
||||||
|
if targeting_hint_back_button != null:
|
||||||
|
targeting_hint_back_button.text = "Back"
|
||||||
|
targeting_hint_back_button.tooltip_text = "Return to the action menu." if _has_pending_move() else "Clear target selection."
|
||||||
|
if targeting_hint_cancel_move_button != null:
|
||||||
|
targeting_hint_cancel_move_button.visible = _has_pending_move()
|
||||||
|
targeting_hint_cancel_move_button.disabled = not _has_pending_move()
|
||||||
|
targeting_hint_cancel_move_button.tooltip_text = "Right-click also returns to the starting cell."
|
||||||
|
targeting_hint_panel.visible = true
|
||||||
|
|
||||||
|
|
||||||
|
func _position_targeting_hint_panel(selected: Dictionary) -> void:
|
||||||
|
if targeting_hint_panel == null:
|
||||||
|
return
|
||||||
|
var cell: Vector2i = selected.get("pos", Vector2i(-1, -1))
|
||||||
|
if not state.is_inside(cell):
|
||||||
|
return
|
||||||
|
var cell_rect := _rect_for_cell(cell)
|
||||||
|
var next_position := cell_rect.position + TARGETING_HINT_PANEL_OFFSET
|
||||||
|
var board_size := Vector2(state.map_size.x, state.map_size.y) * TILE_SIZE
|
||||||
|
var board_min := BOARD_OFFSET + Vector2(4.0, 4.0)
|
||||||
|
var board_max := BOARD_OFFSET + board_size - TARGETING_HINT_PANEL_SIZE - Vector2(4.0, 4.0)
|
||||||
|
board_max.x = maxf(board_min.x, board_max.x)
|
||||||
|
board_max.y = maxf(board_min.y, board_max.y)
|
||||||
|
next_position.x = clampf(next_position.x, board_min.x, board_max.x)
|
||||||
|
next_position.y = clampf(next_position.y, board_min.y, board_max.y)
|
||||||
|
targeting_hint_panel.position = next_position
|
||||||
|
|
||||||
|
|
||||||
|
func _targeting_hint_title_text() -> String:
|
||||||
|
if basic_attack_targeting:
|
||||||
|
return "Select Attack Target"
|
||||||
|
if not selected_skill_id.is_empty():
|
||||||
|
var skill := state.get_skill_def(selected_skill_id)
|
||||||
|
return "Target: %s" % str(skill.get("name", selected_skill_id))
|
||||||
|
if not selected_item_id.is_empty():
|
||||||
|
var item := state.get_item_def(selected_item_id)
|
||||||
|
return "Target: %s" % str(item.get("name", selected_item_id))
|
||||||
|
return "Choose Target"
|
||||||
|
|
||||||
|
|
||||||
|
func _targeting_hint_detail_text() -> String:
|
||||||
|
if basic_attack_targeting:
|
||||||
|
var markers := _attack_target_marker_entries()
|
||||||
|
if markers.is_empty():
|
||||||
|
return "No enemy is in range."
|
||||||
|
return "%d enemy target%s. Click a marked enemy." % [markers.size(), "" if markers.size() == 1 else "s"]
|
||||||
|
if not selected_skill_id.is_empty():
|
||||||
|
return "Click a highlighted tactic cell."
|
||||||
|
if not selected_item_id.is_empty():
|
||||||
|
return "Click a highlighted item target."
|
||||||
|
return "Choose a highlighted target."
|
||||||
|
|
||||||
|
|
||||||
|
func _on_targeting_back_pressed() -> void:
|
||||||
|
_play_ui_cancel()
|
||||||
|
selected_skill_id = ""
|
||||||
|
selected_item_id = ""
|
||||||
|
basic_attack_targeting = false
|
||||||
|
_hide_targeting_hint_panel()
|
||||||
|
_hide_tactic_menu()
|
||||||
|
_hide_item_menu()
|
||||||
|
_hide_equip_menu()
|
||||||
|
if _has_pending_move():
|
||||||
|
_show_post_move_menu()
|
||||||
|
_refresh_ranges()
|
||||||
|
_update_hud()
|
||||||
|
queue_redraw()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_targeting_cancel_move_pressed() -> void:
|
||||||
|
_cancel_pending_move()
|
||||||
|
|
||||||
|
|
||||||
func _set_action_button_state(button: Button, label: String, disabled: bool, tooltip := "") -> void:
|
func _set_action_button_state(button: Button, label: String, disabled: bool, tooltip := "") -> void:
|
||||||
if button == null:
|
if button == null:
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -117,6 +117,95 @@ func _check_scene_post_move_menu_flow(failures: Array[String]) -> void:
|
|||||||
if not scene.state.get_selected_unit().is_empty() or scene._has_pending_move():
|
if not scene.state.get_selected_unit().is_empty() or scene._has_pending_move():
|
||||||
failures.append("post-move Wait should clear selection and pending metadata")
|
failures.append("post-move Wait should clear selection and pending metadata")
|
||||||
|
|
||||||
|
_check_scene_post_move_attack_targeting(failures)
|
||||||
|
|
||||||
|
scene.free()
|
||||||
|
|
||||||
|
|
||||||
|
func _check_scene_post_move_attack_targeting(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 attack targeting flow")
|
||||||
|
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()
|
||||||
|
|
||||||
|
var enemy: Dictionary = scene.state.get_unit("yellow_turban_1")
|
||||||
|
enemy["pos"] = Vector2i(3, 3)
|
||||||
|
enemy["hp"] = 24
|
||||||
|
enemy["def"] = 1
|
||||||
|
var cao_cao: Dictionary = scene.state.get_unit("cao_cao")
|
||||||
|
cao_cao["agi"] = 999
|
||||||
|
cao_cao["atk"] = 60
|
||||||
|
scene.state.rng.seed = 1
|
||||||
|
|
||||||
|
scene.state.battle_events.append({
|
||||||
|
"id": "pending_attack_gold",
|
||||||
|
"once": true,
|
||||||
|
"when": {
|
||||||
|
"type": "unit_reaches_tile",
|
||||||
|
"unit_ids": ["cao_cao"],
|
||||||
|
"pos": [2, 3]
|
||||||
|
},
|
||||||
|
"actions": [{"type": "grant_gold", "amount": 33}]
|
||||||
|
})
|
||||||
|
|
||||||
|
if not scene.state.select_unit("cao_cao"):
|
||||||
|
failures.append("could not select Cao Cao for attack targeting flow")
|
||||||
|
scene.free()
|
||||||
|
return
|
||||||
|
scene._refresh_ranges()
|
||||||
|
scene._handle_board_click(_screen_for_cell(Vector2i(2, 3)))
|
||||||
|
if not scene._has_pending_move() or scene.post_move_menu == null or not scene.post_move_menu.visible:
|
||||||
|
failures.append("attack targeting setup should leave the post-move menu visible")
|
||||||
|
if scene.basic_attack_targeting:
|
||||||
|
failures.append("attack targeting should be off before pressing Attack")
|
||||||
|
if not scene.attack_cells.is_empty():
|
||||||
|
failures.append("attack range should stay hidden before pressing Attack")
|
||||||
|
if scene.state.get_battle_gold_reward() != 0:
|
||||||
|
failures.append("attack setup should not commit pending move events early")
|
||||||
|
|
||||||
|
scene._on_post_move_attack_pressed()
|
||||||
|
if not scene.basic_attack_targeting:
|
||||||
|
failures.append("Attack should enter target selection mode")
|
||||||
|
if scene.post_move_menu != null and scene.post_move_menu.visible:
|
||||||
|
failures.append("post-move menu should hide while selecting an attack target")
|
||||||
|
if scene.targeting_hint_panel == null or not scene.targeting_hint_panel.visible:
|
||||||
|
failures.append("targeting hint panel should be visible during attack targeting")
|
||||||
|
if not scene.attack_cells.has(Vector2i(3, 3)):
|
||||||
|
failures.append("attack targeting should highlight the adjacent enemy cell")
|
||||||
|
var markers := scene._attack_target_marker_entries()
|
||||||
|
if markers.size() != 1 or markers[0].get("cell", Vector2i.ZERO) != Vector2i(3, 3):
|
||||||
|
failures.append("attack target markers should include only the reachable enemy: %s" % str(markers))
|
||||||
|
|
||||||
|
scene._on_targeting_back_pressed()
|
||||||
|
cao_cao = scene.state.get_unit("cao_cao")
|
||||||
|
if scene.basic_attack_targeting or not scene._has_pending_move() or scene.post_move_menu == null or not scene.post_move_menu.visible:
|
||||||
|
failures.append("Back should return to the post-move action menu")
|
||||||
|
if cao_cao.get("pos", Vector2i.ZERO) != Vector2i(2, 3) or not bool(cao_cao.get("moved", false)) or bool(cao_cao.get("acted", false)):
|
||||||
|
failures.append("Back should preserve the pending moved unit without acting")
|
||||||
|
|
||||||
|
scene._on_post_move_attack_pressed()
|
||||||
|
var enemy_hp_before := int(enemy.get("hp", 0))
|
||||||
|
scene._handle_board_click(_screen_for_cell(Vector2i(3, 3)))
|
||||||
|
cao_cao = scene.state.get_unit("cao_cao")
|
||||||
|
enemy = scene.state.get_unit("yellow_turban_1")
|
||||||
|
if scene._has_pending_move() or scene.basic_attack_targeting:
|
||||||
|
failures.append("attack click should clear pending move and targeting state")
|
||||||
|
if not scene.state.get_selected_unit().is_empty():
|
||||||
|
failures.append("attack click should clear selected unit after acting")
|
||||||
|
if cao_cao.get("pos", Vector2i.ZERO) != Vector2i(2, 3) or not bool(cao_cao.get("moved", false)) or not bool(cao_cao.get("acted", false)):
|
||||||
|
failures.append("attack click should commit the moved attacker action")
|
||||||
|
if int(enemy.get("hp", 0)) >= enemy_hp_before and bool(enemy.get("alive", true)):
|
||||||
|
failures.append("attack click should damage or defeat the target")
|
||||||
|
if scene.state.get_battle_gold_reward() != 33:
|
||||||
|
failures.append("attack click should commit deferred movement event before resolving attack")
|
||||||
|
|
||||||
scene.free()
|
scene.free()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user