diff --git a/scripts/scenes/battle_scene.gd b/scripts/scenes/battle_scene.gd index 63ac83a..f3a754b 100644 --- a/scripts/scenes/battle_scene.gd +++ b/scripts/scenes/battle_scene.gd @@ -2117,12 +2117,13 @@ func _format_unit_focus_text(unit: Dictionary, prefix: String) -> String: int(unit.get("int", 0)), int(unit.get("agi", 0)) ]) - lines.append("MOV %d %s RNG %d-%d" % [ + lines.append("Move %d (%s) Attack range %d-%d" % [ int(unit.get("move", 0)), _format_move_type(str(unit.get("move_type", "foot"))), int(unit.get("min_range", 1)), int(unit.get("range", 1)) ]) + lines.append(_unit_action_status_text(unit)) var terrain_text := _unit_current_terrain_text(unit) if not terrain_text.is_empty(): lines.append(terrain_text) @@ -2132,6 +2133,45 @@ func _format_unit_focus_text(unit: Dictionary, prefix: String) -> String: return _join_strings(lines, "\n") +func _unit_action_status_text(unit: Dictionary) -> String: + var unit_id := str(unit.get("id", "")) + if unit_id.is_empty(): + return "Action: Unavailable" + var team := str(unit.get("team", "")) + if team != BattleState.TEAM_PLAYER: + return "Action: Enemy AI" + if not bool(unit.get("controllable", true)): + return "Action: Protected escort" + if not bool(unit.get("alive", true)) or not bool(unit.get("deployed", true)): + return "Action: Unavailable" + if bool(unit.get("acted", false)): + return "Action: Done" + if not state.can_player_act(): + return "Action: Waiting turn" + var actions: Array[String] = [] + if not bool(unit.get("moved", false)) and not state.get_movement_range(unit_id).is_empty(): + actions.append("Move") + if not state.get_attack_cells(unit_id).is_empty(): + actions.append("Attack") + var skill_id := state.get_first_usable_skill_id(unit_id) + if not skill_id.is_empty() and not state.get_skill_cells(unit_id, skill_id).is_empty(): + actions.append("Tactic") + if _unit_has_usable_item_target(unit_id): + actions.append("Item") + if not bool(unit.get("moved", false)): + actions.append("Equip") + actions.append("Wait") + var suffix := " (moved)" if bool(unit.get("moved", false)) else "" + return "Action: %s%s" % [_join_strings(actions, ", "), suffix] + + +func _unit_has_usable_item_target(unit_id: String) -> bool: + for item_id in state.get_usable_item_ids(): + if not state.get_item_target_cells(unit_id, str(item_id)).is_empty(): + return true + return false + + func _unit_role_text(unit: Dictionary) -> String: var class_id := str(unit.get("class_id", "")) if class_id.contains("cavalry"): diff --git a/tools/smoke_visual_assets.gd b/tools/smoke_visual_assets.gd index 4dad7a0..c16d478 100644 --- a/tools/smoke_visual_assets.gd +++ b/tools/smoke_visual_assets.gd @@ -132,6 +132,10 @@ func _check_hud_focus_text(failures: Array[String]) -> void: var cao_text := scene._format_unit_focus_text(cao_cao, "Selected") if not cao_text.contains("Command tactics"): failures.append("Cao Cao HUD focus should describe command role: %s" % cao_text) + if not cao_text.contains("Move 4 (Foot)") or not cao_text.contains("Attack range 1-1"): + failures.append("Cao Cao HUD focus should clarify move/attack affordance: %s" % cao_text) + if not cao_text.contains("Action:") or not cao_text.contains("Move") or not cao_text.contains("Attack") or not cao_text.contains("Tactic") or not cao_text.contains("Equip") or not cao_text.contains("Wait"): + failures.append("Cao Cao HUD focus should summarize available actions: %s" % cao_text) if not cao_text.contains("Tile 2,4"): failures.append("Cao Cao HUD focus should include current tile: %s" % cao_text) if not cao_text.contains("Cost 1"): @@ -147,6 +151,10 @@ func _check_hud_focus_text(failures: Array[String]) -> void: var archer_text := scene._format_unit_focus_text(archer, "Hover") if not archer_text.contains("Ranged pressure"): failures.append("Archer HUD focus should describe ranged role: %s" % archer_text) + if not archer_text.contains("Move 4 (Archer)") or not archer_text.contains("Attack range 2-2"): + failures.append("Archer HUD focus should clarify ranged attack affordance: %s" % archer_text) + if not archer_text.contains("Action: Enemy AI"): + failures.append("Enemy archer HUD focus should describe enemy control: %s" % archer_text) if not scene._unit_current_terrain_text(archer).contains("Plain"): failures.append("Archer terrain text should reflect its current plain tile") scene.free()