Show enemy AI reaction zones

This commit is contained in:
2026-06-20 12:51:08 +09:00
parent 8febd9f917
commit 48160decbb
2 changed files with 142 additions and 0 deletions

View File

@@ -6428,6 +6428,8 @@ func _draw_overlays() -> void:
_draw_objective_seal_marker(objective_rect)
_draw_tactical_tile_overlay("objective", objective_rect, OBJECTIVE_OVERLAY_COLOR, UI_SEAL_RED, 0.56, 0.30, 6.0)
_draw_hovered_ai_zone_overlay()
for cell in threat_cells:
var threat_rect := _rect_for_cell(cell)
_draw_tactical_tile_overlay("attack", threat_rect, THREAT_OVERLAY_COLOR, THREAT_BORDER_COLOR, 0.30, 0.42, 6.0)
@@ -6484,6 +6486,117 @@ func _draw_overlays() -> void:
_draw_tactical_tile_overlay(hover_marker, hover_rect, hover_color, hover_color, 0.44, 0.22, 4.0)
func _draw_hovered_ai_zone_overlay() -> void:
var unit := _hovered_ai_zone_unit()
if unit.is_empty():
return
var cells := _ai_zone_cells_for_unit(unit)
if cells.is_empty():
return
var anchor := _ai_zone_anchor_for_unit(unit)
var radius := _ai_zone_radius_for_unit(unit)
var marker_key := _ai_zone_overlay_marker_key(unit)
var fill := _ai_zone_overlay_fill_color(unit)
var border := _ai_zone_overlay_border_color(unit)
var fade_base := float(maxi(1, radius))
for cell in cells:
if not _cell_is_in_visible_bounds(cell):
continue
var distance := absi(cell.x - anchor.x) + absi(cell.y - anchor.y)
var fade := 1.0 - clampf(float(distance) / fade_base, 0.0, 1.0) * 0.24
var cell_fill := fill
cell_fill.a *= fade
var cell_border := border
cell_border.a *= fade
_draw_tactical_tile_overlay(marker_key, _rect_for_cell(cell), cell_fill, cell_border, 0.13 * fade, 0.36, 8.0)
if state.is_inside(anchor) and _cell_is_in_visible_bounds(anchor):
_draw_tactical_tile_overlay("target", _rect_for_cell(anchor), _ai_zone_anchor_fill_color(unit), _ai_zone_overlay_border_color(unit), 0.25, 0.40, 6.0)
func _hovered_ai_zone_unit() -> Dictionary:
if _is_input_locked() or not battle_started or not state.is_inside(hover_cell):
return {}
var unit := state.get_unit_at(hover_cell)
if unit.is_empty() or str(unit.get("team", "")) != BattleState.TEAM_ENEMY:
return {}
if _ai_zone_radius_for_unit(unit) < 0:
return {}
if not state.is_inside(_ai_zone_anchor_for_unit(unit)):
return {}
return unit
func _ai_zone_cells_for_unit(unit: Dictionary) -> Array[Vector2i]:
var cells: Array[Vector2i] = []
var radius := _ai_zone_radius_for_unit(unit)
var anchor := _ai_zone_anchor_for_unit(unit)
if radius < 0 or not state.is_inside(anchor):
return cells
for y in range(anchor.y - radius, anchor.y + radius + 1):
for x in range(anchor.x - radius, anchor.x + radius + 1):
var cell := Vector2i(x, y)
if not state.is_inside(cell):
continue
if absi(cell.x - anchor.x) + absi(cell.y - anchor.y) <= radius:
cells.append(cell)
return cells
func _ai_zone_anchor_for_unit(unit: Dictionary) -> Vector2i:
var behavior := str(unit.get("ai_behavior", "")).strip_edges().to_lower()
var fallback: Vector2i = unit.get("pos", Vector2i(-1, -1))
var anchor: Vector2i = fallback
if behavior == "guard":
anchor = unit.get("ai_guard_anchor", fallback)
elif behavior == "sentry":
anchor = unit.get("ai_sentry_anchor", fallback)
if not state.is_inside(anchor):
return fallback
return anchor
func _ai_zone_radius_for_unit(unit: Dictionary) -> int:
var behavior := str(unit.get("ai_behavior", "")).strip_edges().to_lower()
if behavior == "guard":
return max(0, int(unit.get("ai_guard_radius", 0)))
if behavior == "sentry":
return max(0, int(unit.get("ai_sentry_radius", 0)))
return -1
func _ai_zone_overlay_marker_key(unit: Dictionary) -> String:
var behavior := str(unit.get("ai_behavior", "")).strip_edges().to_lower()
if behavior == "guard":
return "select"
if behavior == "sentry" and bool(unit.get("ai_awake", false)):
return "attack"
return "move"
func _ai_zone_overlay_fill_color(unit: Dictionary) -> Color:
var behavior := str(unit.get("ai_behavior", "")).strip_edges().to_lower()
if behavior == "guard":
return Color(UI_OLD_BRONZE.r, UI_OLD_BRONZE.g, UI_OLD_BRONZE.b, 0.20)
if behavior == "sentry" and bool(unit.get("ai_awake", false)):
return Color(UI_SEAL_RED.r, UI_SEAL_RED.g, UI_SEAL_RED.b, 0.19)
return Color(UI_MUTED_JADE.r, UI_MUTED_JADE.g, UI_MUTED_JADE.b, 0.20)
func _ai_zone_anchor_fill_color(unit: Dictionary) -> Color:
var color := _ai_zone_overlay_fill_color(unit)
color.a = minf(color.a + 0.10, 0.34)
return color
func _ai_zone_overlay_border_color(unit: Dictionary) -> Color:
var behavior := str(unit.get("ai_behavior", "")).strip_edges().to_lower()
if behavior == "guard":
return Color(UI_OLD_BRONZE.r, UI_OLD_BRONZE.g, UI_OLD_BRONZE.b, 0.42)
if behavior == "sentry" and bool(unit.get("ai_awake", false)):
return Color(UI_SEAL_RED.r, UI_SEAL_RED.g, UI_SEAL_RED.b, 0.38)
return Color(UI_MUTED_JADE.r, UI_MUTED_JADE.g, UI_MUTED_JADE.b, 0.40)
func _draw_units() -> void:
for unit in state.units:
if not unit.get("alive", false) or not unit.get("deployed", true):