Add post-move action menu flow

This commit is contained in:
2026-06-18 21:39:15 +09:00
parent c086e989d7
commit 3add6bab4f
3 changed files with 526 additions and 22 deletions

View File

@@ -59,6 +59,8 @@ const DIALOGUE_COLUMN_SIZE := Vector2(820, 186)
const DIALOGUE_TEXT_SIZE := Vector2(800, 104)
const HUD_UNIT_PORTRAIT_SIZE := Vector2(92, 104)
const HUD_UNIT_PORTRAIT_STACK_SIZE := Vector2(88, 100)
const POST_MOVE_MENU_SIZE := Vector2(228, 120)
const POST_MOVE_MENU_OFFSET := Vector2(20, -34)
var state: BattleState = BattleStateScript.new()
var campaign_state: CampaignState = CampaignStateScript.new()
@@ -70,6 +72,10 @@ var item_cells: Array[Vector2i] = []
var threat_cells: Array[Vector2i] = []
var selected_skill_id := ""
var selected_item_id := ""
var basic_attack_targeting := false
var pending_move_unit_id := ""
var pending_move_from_cell := Vector2i(-1, -1)
var pending_move_to_cell := Vector2i(-1, -1)
var show_threat_overlay := false
var active_scenario_id := ""
var battle_started := false
@@ -158,6 +164,13 @@ var item_list: VBoxContainer
var equip_button: Button
var equip_menu: VBoxContainer
var equip_list: VBoxContainer
var post_move_menu: PanelContainer
var post_move_title_label: Label
var post_move_attack_button: Button
var post_move_tactic_button: Button
var post_move_item_button: Button
var post_move_wait_button: Button
var post_move_cancel_button: Button
var threat_button: Button
var restart_button: Button
var new_campaign_button: Button
@@ -220,6 +233,8 @@ func _unhandled_input(event: InputEvent) -> void:
queue_redraw()
elif event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
_handle_board_click(event.position)
elif event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_RIGHT and event.pressed:
_handle_cancel_input()
elif event is InputEventKey and event.pressed and not event.echo:
_handle_key(event)
@@ -429,6 +444,58 @@ func _create_hud() -> void:
equip_list.add_theme_constant_override("separation", 4)
equip_menu.add_child(equip_list)
post_move_menu = PanelContainer.new()
post_move_menu.visible = false
post_move_menu.mouse_filter = Control.MOUSE_FILTER_STOP
post_move_menu.custom_minimum_size = POST_MOVE_MENU_SIZE
post_move_menu.size = POST_MOVE_MENU_SIZE
root.add_child(post_move_menu)
var post_move_column := VBoxContainer.new()
post_move_column.add_theme_constant_override("separation", 4)
post_move_menu.add_child(post_move_column)
post_move_title_label = Label.new()
post_move_title_label.text = "Choose Action"
post_move_title_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
post_move_column.add_child(post_move_title_label)
var post_move_grid := GridContainer.new()
post_move_grid.columns = 2
post_move_grid.add_theme_constant_override("h_separation", 6)
post_move_grid.add_theme_constant_override("v_separation", 4)
post_move_column.add_child(post_move_grid)
post_move_attack_button = Button.new()
post_move_attack_button.text = "Attack"
post_move_attack_button.tooltip_text = "Choose an enemy target from this unit's current position."
post_move_attack_button.pressed.connect(_on_post_move_attack_pressed)
post_move_grid.add_child(post_move_attack_button)
post_move_tactic_button = Button.new()
post_move_tactic_button.text = "Tactic"
post_move_tactic_button.tooltip_text = "Open tactics after this move."
post_move_tactic_button.pressed.connect(_on_post_move_tactic_pressed)
post_move_grid.add_child(post_move_tactic_button)
post_move_item_button = Button.new()
post_move_item_button.text = "Item"
post_move_item_button.tooltip_text = "Open consumable items after this move."
post_move_item_button.pressed.connect(_on_post_move_item_pressed)
post_move_grid.add_child(post_move_item_button)
post_move_wait_button = Button.new()
post_move_wait_button.text = "Wait"
post_move_wait_button.tooltip_text = "Commit this move and end the unit's action."
post_move_wait_button.pressed.connect(_on_post_move_wait_pressed)
post_move_grid.add_child(post_move_wait_button)
post_move_cancel_button = Button.new()
post_move_cancel_button.text = "Cancel Move"
post_move_cancel_button.tooltip_text = "Return to the starting cell."
post_move_cancel_button.pressed.connect(_on_post_move_cancel_pressed)
post_move_column.add_child(post_move_cancel_button)
log_box = RichTextLabel.new()
log_box.custom_minimum_size = Vector2(420, 70)
log_box.fit_content = false
@@ -938,50 +1005,173 @@ func _handle_board_click(screen_position: Vector2) -> void:
if _can_select(clicked_unit):
selected_skill_id = ""
selected_item_id = ""
basic_attack_targeting = false
_clear_pending_move_state()
state.select_unit(clicked_unit["id"])
return
if not selected_item_id.is_empty():
if state.try_use_selected_item_on_cell(selected_item_id, cell):
if item_cells.has(cell) and _commit_pending_move_before_action() and state.try_use_selected_item_on_cell(selected_item_id, cell):
selected_item_id = ""
basic_attack_targeting = false
_clear_pending_move_state()
return
if _has_pending_move():
return
if not clicked_unit.is_empty() and clicked_unit.get("team", "") == selected_unit.get("team", "") and _can_select(clicked_unit):
selected_item_id = ""
basic_attack_targeting = false
state.select_unit(clicked_unit["id"])
return
if not selected_skill_id.is_empty():
if state.try_cast_selected_skill(selected_skill_id, cell):
if skill_cells.has(cell) and _commit_pending_move_before_action() and state.try_cast_selected_skill(selected_skill_id, cell):
selected_skill_id = ""
basic_attack_targeting = false
_clear_pending_move_state()
return
if _has_pending_move():
return
if not clicked_unit.is_empty() and clicked_unit.get("team", "") == selected_unit.get("team", "") and _can_select(clicked_unit):
selected_skill_id = ""
selected_item_id = ""
basic_attack_targeting = false
state.select_unit(clicked_unit["id"])
return
if not clicked_unit.is_empty() and clicked_unit.get("team", "") != selected_unit.get("team", ""):
if state.try_attack_selected(clicked_unit["id"]):
if _has_pending_move() and not basic_attack_targeting:
return
var target_cell: Vector2i = clicked_unit.get("pos", cell)
if attack_cells.has(target_cell) and _commit_pending_move_before_action() and state.try_attack_selected(clicked_unit["id"]):
selected_skill_id = ""
selected_item_id = ""
basic_attack_targeting = false
_clear_pending_move_state()
return
if not clicked_unit.is_empty() and clicked_unit.get("team", "") == selected_unit.get("team", ""):
if _has_pending_move():
return
if _can_select(clicked_unit):
selected_skill_id = ""
selected_item_id = ""
basic_attack_targeting = false
_clear_pending_move_state()
state.select_unit(clicked_unit["id"])
return
if state.try_move_selected(cell):
var selected_start_cell: Vector2i = selected_unit.get("pos", Vector2i(-1, -1))
if not _has_pending_move() and state.try_move_selected(cell, true):
pending_move_unit_id = str(selected_unit.get("id", ""))
pending_move_from_cell = selected_start_cell
pending_move_to_cell = cell
selected_skill_id = ""
selected_item_id = ""
basic_attack_targeting = false
_hide_tactic_menu()
_hide_item_menu()
_hide_equip_menu()
_refresh_ranges()
_show_post_move_menu()
_update_hud()
queue_redraw()
return
if _can_select(clicked_unit):
selected_skill_id = ""
selected_item_id = ""
basic_attack_targeting = false
_clear_pending_move_state()
state.select_unit(clicked_unit["id"])
func _has_pending_move() -> bool:
return (
not pending_move_unit_id.is_empty()
and pending_move_from_cell != Vector2i(-1, -1)
and pending_move_to_cell != Vector2i(-1, -1)
)
func _clear_pending_move_state(hide_menu := true) -> void:
pending_move_unit_id = ""
pending_move_from_cell = Vector2i(-1, -1)
pending_move_to_cell = Vector2i(-1, -1)
basic_attack_targeting = false
if hide_menu:
_hide_post_move_menu()
func _commit_pending_move_before_action() -> bool:
if not _has_pending_move():
return true
var unit_id := pending_move_unit_id
_clear_pending_move_state()
var still_active := state.commit_pending_move_events(unit_id)
return still_active and state.can_player_act() and not state.get_selected_unit().is_empty() and not _is_input_locked()
func _cancel_pending_move() -> bool:
if not _has_pending_move():
return false
var unit_id := pending_move_unit_id
var from_cell := pending_move_from_cell
var to_cell := pending_move_to_cell
selected_skill_id = ""
selected_item_id = ""
basic_attack_targeting = false
_hide_tactic_menu()
_hide_item_menu()
_hide_equip_menu()
_hide_post_move_menu()
if state.cancel_pending_move(unit_id, from_cell, to_cell):
_clear_pending_move_state()
_play_ui_cancel()
_refresh_ranges()
_update_hud()
queue_redraw()
return true
return false
func _handle_cancel_input() -> bool:
if _has_pending_move():
return _cancel_pending_move()
if tactic_menu != null and tactic_menu.visible:
_play_ui_cancel()
_hide_tactic_menu()
_update_hud()
queue_redraw()
return true
if item_menu != null and item_menu.visible:
_play_ui_cancel()
_hide_item_menu()
_update_hud()
queue_redraw()
return true
if equip_menu != null and equip_menu.visible:
_play_ui_cancel()
_hide_equip_menu()
_update_hud()
queue_redraw()
return true
if not selected_skill_id.is_empty() or not selected_item_id.is_empty() or basic_attack_targeting:
_play_ui_cancel()
selected_skill_id = ""
selected_item_id = ""
basic_attack_targeting = false
_refresh_ranges()
_update_hud()
queue_redraw()
return true
if not state.get_selected_unit().is_empty():
_play_ui_cancel()
state.clear_selection()
return true
return false
func _handle_key(event: InputEventKey) -> void:
if event.keycode == KEY_ENTER and briefing_panel != null and briefing_panel.visible:
if _is_prep_menu_visible():
@@ -1006,21 +1196,10 @@ func _handle_key(event: InputEventKey) -> void:
_hide_item_menu()
elif equip_menu != null and equip_menu.visible:
_hide_equip_menu()
elif selected_skill_id.is_empty() and selected_item_id.is_empty():
state.clear_selection()
else:
selected_skill_id = ""
selected_item_id = ""
_refresh_ranges()
_update_hud()
queue_redraw()
_handle_cancel_input()
elif event.keycode == KEY_SPACE and not _is_input_locked():
selected_skill_id = ""
selected_item_id = ""
_hide_tactic_menu()
_hide_item_menu()
_hide_equip_menu()
state.end_player_turn()
_on_end_turn_pressed()
elif event.keycode == KEY_R and not _is_prep_menu_visible():
_on_restart_pressed()
elif event.keycode == KEY_T and not _is_input_locked():
@@ -1346,6 +1525,11 @@ func _draw_overlays() -> void:
for cell in move_cells:
draw_rect(_rect_for_cell(cell), Color(0.16, 0.55, 0.95, 0.28))
if _has_pending_move() and state.is_inside(pending_move_to_cell):
var pending_rect := _rect_for_cell(pending_move_to_cell)
draw_rect(pending_rect.grow(-4.0), Color(1.0, 0.82, 0.28, 0.32))
draw_rect(pending_rect.grow(-7.0), Color(1.0, 0.88, 0.36, 0.86), false, 2.0)
for cell in attack_cells:
draw_rect(_rect_for_cell(cell), Color(0.95, 0.22, 0.18, 0.24))
@@ -1847,6 +2031,8 @@ func _target_preview_badge() -> Dictionary:
return _skill_target_preview_badge(selected, target)
if not selected_item_id.is_empty():
return _item_target_preview_badge(selected, target)
if _has_pending_move() and not basic_attack_targeting:
return {}
var attack_badge := _attack_target_preview_badge(selected, target)
if not attack_badge.is_empty():
return attack_badge
@@ -1942,6 +2128,8 @@ func _item_target_preview_badge(selected: Dictionary, target: Dictionary) -> Dic
func _hover_intent_preview_badge(selected: Dictionary, target: Dictionary) -> Dictionary:
if selected.is_empty():
return {}
if _has_pending_move():
return {}
if not state.can_player_act():
return {}
if not bool(selected.get("controllable", true)) or bool(selected.get("acted", false)):
@@ -2039,20 +2227,24 @@ func _refresh_ranges() -> void:
if selected.is_empty():
selected_skill_id = ""
selected_item_id = ""
basic_attack_targeting = false
return
move_cells = state.get_movement_range(selected["id"])
if not selected_skill_id.is_empty():
skill_cells = state.get_skill_cells(selected["id"], selected_skill_id)
if skill_cells.is_empty():
selected_skill_id = ""
attack_cells = state.get_attack_cells(selected["id"])
if not _has_pending_move() or basic_attack_targeting:
attack_cells = state.get_attack_cells(selected["id"])
elif not selected_item_id.is_empty():
item_cells = state.get_item_target_cells(selected["id"], selected_item_id)
if item_cells.is_empty():
selected_item_id = ""
attack_cells = state.get_attack_cells(selected["id"])
if not _has_pending_move() or basic_attack_targeting:
attack_cells = state.get_attack_cells(selected["id"])
else:
attack_cells = state.get_attack_cells(selected["id"])
if not _has_pending_move() or basic_attack_targeting:
attack_cells = state.get_attack_cells(selected["id"])
func _update_hud() -> void:
@@ -2098,6 +2290,7 @@ func _update_hud() -> void:
_update_end_turn_button()
restart_button.disabled = campaign_complete_screen
_update_threat_button()
_update_post_move_menu()
func _format_objective_hud_text() -> String:
@@ -2666,6 +2859,7 @@ func _on_state_changed() -> void:
if state.get_selected_unit().is_empty():
selected_skill_id = ""
selected_item_id = ""
_clear_pending_move_state()
_refresh_ranges()
_update_hud()
queue_redraw()
@@ -2914,9 +3108,11 @@ func _show_next_dialogue() -> void:
return
selected_skill_id = ""
selected_item_id = ""
basic_attack_targeting = false
_hide_tactic_menu()
_hide_item_menu()
_hide_equip_menu()
_hide_post_move_menu()
_refresh_ranges()
active_dialogue_lines = dialogue_queue.pop_front()
active_dialogue_index = 0
@@ -3066,18 +3262,29 @@ func _is_dialogue_visible() -> bool:
func _on_wait_pressed() -> void:
selected_skill_id = ""
selected_item_id = ""
basic_attack_targeting = false
_hide_tactic_menu()
_hide_item_menu()
_hide_equip_menu()
if state.try_wait_selected():
if _commit_pending_move_before_action() and state.try_wait_selected():
_play_ui_confirm()
func _on_end_turn_pressed() -> void:
if state.can_player_act():
_play_ui_confirm()
if _has_pending_move():
selected_skill_id = ""
selected_item_id = ""
basic_attack_targeting = false
_hide_tactic_menu()
_hide_item_menu()
_hide_equip_menu()
if not (_commit_pending_move_before_action() and state.try_wait_selected()):
return
selected_skill_id = ""
selected_item_id = ""
basic_attack_targeting = false
_hide_tactic_menu()
_hide_item_menu()
_hide_equip_menu()
@@ -3091,6 +3298,7 @@ func _on_tactic_pressed() -> void:
if state.is_unit_skill_sealed(selected["id"]):
_play_ui_cancel()
selected_skill_id = ""
basic_attack_targeting = false
_hide_tactic_menu()
_refresh_ranges()
_update_hud()
@@ -3099,8 +3307,12 @@ func _on_tactic_pressed() -> void:
_play_ui_click()
if tactic_menu != null and tactic_menu.visible:
_hide_tactic_menu()
if _has_pending_move():
_show_post_move_menu()
else:
selected_item_id = ""
basic_attack_targeting = false
_hide_post_move_menu()
_hide_item_menu()
_hide_equip_menu()
_show_tactic_menu(selected)
@@ -3116,8 +3328,12 @@ func _on_item_pressed() -> void:
_play_ui_click()
if item_menu != null and item_menu.visible:
_hide_item_menu()
if _has_pending_move():
_show_post_move_menu()
else:
selected_skill_id = ""
basic_attack_targeting = false
_hide_post_move_menu()
_hide_tactic_menu()
_hide_equip_menu()
_show_item_menu(selected)
@@ -3138,8 +3354,10 @@ func _on_equip_pressed() -> void:
else:
selected_skill_id = ""
selected_item_id = ""
basic_attack_targeting = false
_hide_tactic_menu()
_hide_item_menu()
_hide_post_move_menu()
_show_equip_menu(selected)
_refresh_ranges()
_update_hud()
@@ -3172,6 +3390,7 @@ func _on_restart_pressed() -> void:
shop_sell_mode = false
selected_skill_id = ""
selected_item_id = ""
_clear_pending_move_state()
armory_unit_id = ""
formation_unit_id = ""
_hide_tactic_menu()
@@ -3216,6 +3435,7 @@ func _load_scenario(scenario_id: String) -> void:
unit_motion_by_unit.clear()
selected_skill_id = ""
selected_item_id = ""
_clear_pending_move_state()
_hide_tactic_menu()
_hide_item_menu()
_hide_equip_menu()
@@ -3358,6 +3578,7 @@ func _show_briefing() -> void:
_hide_roster_menu()
_hide_formation_menu()
_hide_save_menu()
_clear_pending_move_state()
var prep_locked := _is_prebattle_prep_locked()
if chapter_button != null:
chapter_button.disabled = campaign_state.get_chapter_progress_entries().is_empty()
@@ -3392,6 +3613,7 @@ func _start_battle_from_briefing() -> void:
_hide_roster_menu()
_hide_formation_menu()
_hide_save_menu()
_clear_pending_move_state()
state.run_battle_begin_events()
_show_next_dialogue()
_update_hud()
@@ -3914,6 +4136,7 @@ func _reload_from_campaign_state() -> void:
unit_motion_by_unit.clear()
selected_skill_id = ""
selected_item_id = ""
_clear_pending_move_state()
armory_unit_id = ""
formation_unit_id = ""
_hide_tactic_menu()
@@ -4497,6 +4720,7 @@ func _try_show_post_battle_dialogue() -> bool:
dialogue_queue.clear()
selected_skill_id = ""
selected_item_id = ""
_clear_pending_move_state()
_hide_tactic_menu()
_hide_item_menu()
_hide_equip_menu()
@@ -4834,6 +5058,7 @@ func _show_campaign_complete() -> void:
item_cells.clear()
selected_skill_id = ""
selected_item_id = ""
_clear_pending_move_state()
_hide_tactic_menu()
_hide_item_menu()
_hide_equip_menu()
@@ -4870,6 +5095,7 @@ func _on_new_campaign_pressed() -> void:
post_battle_dialogue_started = false
selected_skill_id = ""
selected_item_id = ""
_clear_pending_move_state()
formation_unit_id = ""
_hide_tactic_menu()
_hide_item_menu()
@@ -4885,6 +5111,119 @@ func _on_new_campaign_pressed() -> void:
_update_hud()
func _show_post_move_menu() -> void:
if post_move_menu == null or not _has_pending_move():
return
_update_post_move_menu()
post_move_menu.visible = true
post_move_menu.move_to_front()
func _hide_post_move_menu() -> void:
if post_move_menu == null:
return
post_move_menu.visible = false
func _update_post_move_menu() -> void:
if post_move_menu == null:
return
if not _has_pending_move():
_hide_post_move_menu()
return
var selected := state.get_selected_unit()
if selected.is_empty() or str(selected.get("id", "")) != pending_move_unit_id or bool(selected.get("acted", false)):
_hide_post_move_menu()
return
_position_post_move_menu()
if post_move_title_label != null:
post_move_title_label.text = "%s: choose action" % str(selected.get("name", "Unit"))
if post_move_attack_button != null:
var attack_blocked := state.get_attack_cells(pending_move_unit_id).is_empty()
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."
if post_move_tactic_button != null:
var skill_ids := state.get_skill_ids(pending_move_unit_id)
var skill_blocked := skill_ids.is_empty() or state.is_unit_skill_sealed(pending_move_unit_id) or state.get_first_usable_skill_id(pending_move_unit_id).is_empty()
post_move_tactic_button.disabled = skill_blocked
post_move_tactic_button.tooltip_text = "No usable tactic is available after this move." if skill_blocked else "Open tactics after this move."
if post_move_item_button != null:
var item_blocked := not _unit_has_usable_item_target(pending_move_unit_id)
post_move_item_button.disabled = item_blocked
post_move_item_button.tooltip_text = "No usable item target is in range." if item_blocked else "Open consumable items after this move."
if post_move_wait_button != null:
post_move_wait_button.disabled = false
post_move_wait_button.tooltip_text = "Commit this move and end the unit's action."
if post_move_cancel_button != null:
post_move_cancel_button.disabled = false
post_move_cancel_button.tooltip_text = "Right-click also returns to the starting cell."
func _position_post_move_menu() -> void:
if post_move_menu == null:
return
var cell_rect := _rect_for_cell(pending_move_to_cell)
var next_position := cell_rect.position + Vector2(TILE_SIZE, 0.0) + POST_MOVE_MENU_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 - POST_MOVE_MENU_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)
post_move_menu.position = next_position
func _on_post_move_attack_pressed() -> void:
if not _has_pending_move():
return
if state.get_attack_cells(pending_move_unit_id).is_empty():
_play_ui_cancel()
return
_play_ui_click()
selected_skill_id = ""
selected_item_id = ""
basic_attack_targeting = true
_hide_tactic_menu()
_hide_item_menu()
_hide_equip_menu()
_hide_post_move_menu()
_refresh_ranges()
_update_hud()
queue_redraw()
func _on_post_move_tactic_pressed() -> void:
if not _has_pending_move():
return
_hide_post_move_menu()
basic_attack_targeting = false
_on_tactic_pressed()
func _on_post_move_item_pressed() -> void:
if not _has_pending_move():
return
_hide_post_move_menu()
basic_attack_targeting = false
_on_item_pressed()
func _on_post_move_wait_pressed() -> void:
selected_skill_id = ""
selected_item_id = ""
basic_attack_targeting = false
_hide_tactic_menu()
_hide_item_menu()
_hide_equip_menu()
if _commit_pending_move_before_action() and state.try_wait_selected():
_play_ui_confirm()
func _on_post_move_cancel_pressed() -> void:
_cancel_pending_move()
func _set_action_button_state(button: Button, label: String, disabled: bool, tooltip := "") -> void:
if button == null:
return
@@ -5129,9 +5468,11 @@ func _on_tactic_skill_pressed(skill_id: String) -> void:
_play_ui_confirm()
selected_skill_id = skill_id
selected_item_id = ""
basic_attack_targeting = false
_hide_tactic_menu()
_hide_item_menu()
_hide_equip_menu()
_hide_post_move_menu()
_refresh_ranges()
_update_hud()
queue_redraw()
@@ -5140,7 +5481,10 @@ func _on_tactic_skill_pressed(skill_id: String) -> void:
func _on_tactic_cancel_pressed() -> void:
_play_ui_cancel()
selected_skill_id = ""
basic_attack_targeting = false
_hide_tactic_menu()
if _has_pending_move():
_show_post_move_menu()
_refresh_ranges()
_update_hud()
queue_redraw()
@@ -5252,9 +5596,11 @@ func _on_item_selected_pressed(item_id: String) -> void:
_play_ui_confirm()
selected_item_id = item_id
selected_skill_id = ""
basic_attack_targeting = false
_hide_item_menu()
_hide_tactic_menu()
_hide_equip_menu()
_hide_post_move_menu()
_refresh_ranges()
_update_hud()
queue_redraw()
@@ -5263,7 +5609,10 @@ func _on_item_selected_pressed(item_id: String) -> void:
func _on_item_cancel_pressed() -> void:
_play_ui_cancel()
selected_item_id = ""
basic_attack_targeting = false
_hide_item_menu()
if _has_pending_move():
_show_post_move_menu()
_refresh_ranges()
_update_hud()
queue_redraw()