Explain disabled battle action buttons

This commit is contained in:
2026-06-18 21:18:57 +09:00
parent 1e75b50bcd
commit c086e989d7
2 changed files with 205 additions and 29 deletions

View File

@@ -2074,7 +2074,7 @@ func _update_hud() -> void:
selected_label.text = "No unit selected."
else:
selected_label.text = _format_unit_focus_text(hovered_unit, "Hover")
wait_button.disabled = true
_update_wait_button({})
_update_tactic_button({})
_hide_tactic_menu()
_update_item_button({})
@@ -2084,7 +2084,7 @@ func _update_hud() -> void:
else:
_update_hud_unit_portrait(selected)
selected_label.text = _format_unit_focus_text(selected, "Selected")
wait_button.disabled = selected.get("acted", false) or not state.can_player_act() or _is_input_locked()
_update_wait_button(selected)
_update_tactic_button(selected)
_update_item_button(selected)
_update_equip_button(selected)
@@ -2095,11 +2095,9 @@ func _update_hud() -> void:
if equip_menu != null and equip_menu.visible:
_rebuild_equip_menu(selected)
end_turn_button.disabled = not state.can_player_act() or _is_input_locked()
_update_end_turn_button()
restart_button.disabled = campaign_complete_screen
if threat_button != null:
threat_button.button_pressed = show_threat_overlay
threat_button.disabled = not battle_started or state.battle_status != BattleState.STATUS_ACTIVE or campaign_complete_screen
_update_threat_button()
func _format_objective_hud_text() -> String:
@@ -4887,32 +4885,118 @@ func _on_new_campaign_pressed() -> void:
_update_hud()
func _set_action_button_state(button: Button, label: String, disabled: bool, tooltip := "") -> void:
if button == null:
return
button.text = label
button.disabled = disabled
button.tooltip_text = tooltip
func _set_action_button_blocked(button: Button, base_label: String, reason_label: String, tooltip: String) -> void:
_set_action_button_state(button, "%s - %s" % [base_label, reason_label], true, tooltip)
func _action_phase_block_reason() -> Dictionary:
if campaign_complete_screen:
return {
"label": "Campaign Done",
"tooltip": "The campaign is complete."
}
if not battle_started:
return {
"label": "Start Battle",
"tooltip": "Begin the battle before using combat actions."
}
if state.battle_status != BattleState.STATUS_ACTIVE:
return {
"label": "Battle Over",
"tooltip": "Combat actions are unavailable after the battle ends."
}
if state.current_team != BattleState.TEAM_PLAYER:
return {
"label": "Enemy Turn",
"tooltip": "Wait for the player phase."
}
if _is_input_locked():
return {
"label": "Busy",
"tooltip": "Finish the current dialogue, result, or animation first."
}
return {}
func _update_wait_button(selected: Dictionary) -> void:
if wait_button == null:
return
if selected.is_empty():
_set_action_button_blocked(wait_button, "Wait", "Select Unit", "Select a controllable player unit before waiting.")
return
if bool(selected.get("acted", false)):
_set_action_button_blocked(wait_button, "Wait", "Done", "This unit has already acted.")
return
var phase_block := _action_phase_block_reason()
if not phase_block.is_empty():
_set_action_button_blocked(wait_button, "Wait", str(phase_block.get("label", "Busy")), str(phase_block.get("tooltip", "")))
return
_set_action_button_state(wait_button, "Wait", false, "End this unit's action for the current turn.")
func _update_end_turn_button() -> void:
if end_turn_button == null:
return
var phase_block := _action_phase_block_reason()
if not phase_block.is_empty():
_set_action_button_blocked(end_turn_button, "End Turn", str(phase_block.get("label", "Busy")), str(phase_block.get("tooltip", "")))
return
_set_action_button_state(end_turn_button, "End Turn", false, "End the player phase and let enemies act.")
func _update_threat_button() -> void:
if threat_button == null:
return
threat_button.button_pressed = show_threat_overlay
if campaign_complete_screen:
_set_action_button_blocked(threat_button, "Threat", "Campaign Done", "The campaign is complete.")
return
if not battle_started:
_set_action_button_blocked(threat_button, "Threat", "Start Battle", "Begin the battle before showing enemy threat range.")
return
if state.battle_status != BattleState.STATUS_ACTIVE:
_set_action_button_blocked(threat_button, "Threat", "Battle Over", "Threat range is only available during active battles.")
return
_set_action_button_state(threat_button, "Threat", false, "Toggle enemy attack and hostile tactic reach.")
func _update_tactic_button(selected: Dictionary) -> void:
if tactic_button == null:
return
if selected.is_empty():
tactic_button.text = "Tactic"
tactic_button.disabled = true
_set_action_button_blocked(tactic_button, "Tactic", "Select Unit", "Select a controllable player unit before using tactics.")
return
var skill_ids := state.get_skill_ids(selected["id"])
if skill_ids.is_empty():
tactic_button.text = "Tactic"
tactic_button.disabled = true
_set_action_button_blocked(tactic_button, "Tactic", "None", "This unit has no tactics.")
return
if state.is_unit_skill_sealed(selected["id"]):
tactic_button.text = "Tactic - Sealed"
tactic_button.disabled = true
_set_action_button_blocked(tactic_button, "Tactic", "Sealed", "This unit is sealed and cannot use tactics.")
return
if bool(selected.get("acted", false)):
_set_action_button_blocked(tactic_button, "Tactic", "Done", "This unit has already acted.")
return
var phase_block := _action_phase_block_reason()
if not phase_block.is_empty():
_set_action_button_blocked(tactic_button, "Tactic", str(phase_block.get("label", "Busy")), str(phase_block.get("tooltip", "")))
return
if not selected_skill_id.is_empty():
var skill := state.get_skill_def(selected_skill_id)
tactic_button.text = "Tactic: %s" % str(skill.get("name", selected_skill_id))
_set_action_button_state(tactic_button, "Tactic: %s" % str(skill.get("name", selected_skill_id)), false, "Target this tactic on the map.")
elif tactic_menu != null and tactic_menu.visible:
tactic_button.text = "Close Tactic"
_set_action_button_state(tactic_button, "Close Tactic", false, "Close the tactics menu.")
else:
tactic_button.text = "Tactic"
tactic_button.disabled = selected.get("acted", false) or not state.can_player_act() or _is_input_locked()
_set_action_button_state(tactic_button, "Tactic", false, "Open tactics. %d known." % skill_ids.size())
func _show_tactic_menu(selected: Dictionary) -> void:
@@ -5066,24 +5150,28 @@ func _update_item_button(selected: Dictionary) -> void:
if item_button == null:
return
if selected.is_empty():
item_button.text = "Item"
item_button.disabled = true
_set_action_button_blocked(item_button, "Item", "Select Unit", "Select a controllable player unit before using items.")
return
if bool(selected.get("acted", false)):
_set_action_button_blocked(item_button, "Item", "Done", "This unit has already acted.")
return
var phase_block := _action_phase_block_reason()
if not phase_block.is_empty():
_set_action_button_blocked(item_button, "Item", str(phase_block.get("label", "Busy")), str(phase_block.get("tooltip", "")))
return
var item_ids := state.get_usable_item_ids()
if item_ids.is_empty():
item_button.text = "Item"
item_button.disabled = true
_set_action_button_blocked(item_button, "Item", "None", "No consumable items are available.")
return
if not selected_item_id.is_empty():
var item := state.get_item_def(selected_item_id)
item_button.text = "Item: %s" % str(item.get("name", selected_item_id))
_set_action_button_state(item_button, "Item: %s" % str(item.get("name", selected_item_id)), false, "Target this item on the map.")
elif item_menu != null and item_menu.visible:
item_button.text = "Close Item"
_set_action_button_state(item_button, "Close Item", false, "Close the item menu.")
else:
item_button.text = "Item"
item_button.disabled = selected.get("acted", false) or not state.can_player_act() or _is_input_locked()
_set_action_button_state(item_button, "Item", false, "Open items. %d consumable types available." % item_ids.size())
func _show_item_menu(selected: Dictionary) -> void:
@@ -5185,15 +5273,23 @@ func _update_equip_button(selected: Dictionary) -> void:
if equip_button == null:
return
if selected.is_empty():
equip_button.text = "Equip"
equip_button.disabled = true
_set_action_button_blocked(equip_button, "Equip", "Select Unit", "Select a controllable player unit before changing equipment.")
return
if bool(selected.get("acted", false)):
_set_action_button_blocked(equip_button, "Equip", "Done", "This unit has already acted.")
return
if bool(selected.get("moved", false)):
_set_action_button_blocked(equip_button, "Equip", "Moved", "Equipment can only be changed before this unit moves.")
return
var phase_block := _action_phase_block_reason()
if not phase_block.is_empty():
_set_action_button_blocked(equip_button, "Equip", str(phase_block.get("label", "Busy")), str(phase_block.get("tooltip", "")))
return
if equip_menu != null and equip_menu.visible:
equip_button.text = "Close Equip"
_set_action_button_state(equip_button, "Close Equip", false, "Close the equipment menu.")
else:
equip_button.text = "Equip"
equip_button.disabled = selected.get("moved", false) or selected.get("acted", false) or not state.can_player_act() or _is_input_locked()
_set_action_button_state(equip_button, "Equip", false, "Change weapons, armor, or accessories before moving.")
func _show_equip_menu(selected: Dictionary) -> void:

View File

@@ -44,6 +44,7 @@ func _init() -> void:
_check_scene_texture_loading(failures)
_check_hud_focus_text(failures)
_check_hover_intent_badges(failures)
_check_action_button_disabled_reasons(failures)
_check_terrain_and_unit_presentation(failures)
_check_attack_motion_profiles(failures)
_check_attack_motion_signal(failures)
@@ -237,6 +238,85 @@ func _check_hud_focus_text(failures: Array[String]) -> void:
scene.free()
func _check_action_button_disabled_reasons(failures: Array[String]) -> void:
var scene = BattleSceneScript.new()
scene.wait_button = Button.new()
scene.tactic_button = Button.new()
scene.item_button = Button.new()
scene.equip_button = Button.new()
scene.end_turn_button = Button.new()
scene.threat_button = Button.new()
if not scene.state.load_battle("res://data/scenarios/001_yellow_turbans.json"):
failures.append("could not load battle scene state for action button reasons")
_free_action_button_test_scene(scene)
return
scene.battle_started = true
var cao_cao: Dictionary = scene.state.get_unit("cao_cao")
cao_cao["status_effects"] = [{
"type": "action_lock",
"status": "seal",
"action": "skill",
"remaining_phases": 2
}]
scene._update_tactic_button(cao_cao)
if scene.tactic_button.text != "Tactic - Sealed" or not scene.tactic_button.tooltip_text.to_lower().contains("sealed"):
failures.append("sealed unit should explain disabled tactic button: %s | %s" % [scene.tactic_button.text, scene.tactic_button.tooltip_text])
cao_cao["status_effects"] = []
cao_cao["acted"] = true
scene._update_wait_button(cao_cao)
scene._update_tactic_button(cao_cao)
scene._update_item_button(cao_cao)
scene._update_equip_button(cao_cao)
if not scene.wait_button.text.contains("Done"):
failures.append("acted unit wait button should show Done: %s" % scene.wait_button.text)
if not scene.tactic_button.text.contains("Done"):
failures.append("acted unit tactic button should show Done: %s" % scene.tactic_button.text)
if not scene.item_button.text.contains("Done"):
failures.append("acted unit item button should show Done: %s" % scene.item_button.text)
if not scene.equip_button.text.contains("Done"):
failures.append("acted unit equip button should show Done: %s" % scene.equip_button.text)
cao_cao["acted"] = false
cao_cao["moved"] = true
scene._update_equip_button(cao_cao)
if scene.equip_button.text != "Equip - Moved" or not scene.equip_button.tooltip_text.to_lower().contains("before"):
failures.append("moved unit should explain disabled equip button: %s | %s" % [scene.equip_button.text, scene.equip_button.tooltip_text])
cao_cao["moved"] = false
scene.state.set_inventory_snapshot({})
scene._update_item_button(cao_cao)
if scene.item_button.text != "Item - None" or not scene.item_button.tooltip_text.to_lower().contains("no consumable"):
failures.append("empty inventory should explain disabled item button: %s | %s" % [scene.item_button.text, scene.item_button.tooltip_text])
scene.state.current_team = BattleState.TEAM_ENEMY
scene._update_end_turn_button()
if scene.end_turn_button.text != "End Turn - Enemy Turn" or not scene.end_turn_button.tooltip_text.to_lower().contains("player phase"):
failures.append("enemy phase should explain disabled end turn button: %s | %s" % [scene.end_turn_button.text, scene.end_turn_button.tooltip_text])
scene.state.current_team = BattleState.TEAM_PLAYER
scene.battle_started = false
scene._update_threat_button()
if scene.threat_button.text != "Threat - Start Battle" or not scene.threat_button.tooltip_text.to_lower().contains("begin"):
failures.append("inactive battle should explain disabled threat button: %s | %s" % [scene.threat_button.text, scene.threat_button.tooltip_text])
_free_action_button_test_scene(scene)
func _free_action_button_test_scene(scene) -> void:
for button in [
scene.wait_button,
scene.tactic_button,
scene.item_button,
scene.equip_button,
scene.end_turn_button,
scene.threat_button
]:
if button != null and is_instance_valid(button):
button.free()
scene.free()
func _check_hover_intent_badges(failures: Array[String]) -> void:
var scene = BattleSceneScript.new()
if not scene.state.load_battle("res://data/scenarios/001_yellow_turbans.json"):