Add guard behavior for opening defenders

This commit is contained in:
2026-06-19 19:06:50 +09:00
parent f13f20939f
commit 8f1b6fb398
6 changed files with 232 additions and 3 deletions

View File

@@ -606,6 +606,16 @@ func _prepare_unit(source_unit: Dictionary) -> Dictionary:
unit["controllable"] = bool(unit.get("controllable", true))
unit["persist_progression"] = bool(unit.get("persist_progression", true))
unit["ai_target_priority"] = max(0, int(unit.get("ai_target_priority", 0)))
unit["ai_behavior"] = str(unit.get("ai_behavior", "")).strip_edges().to_lower()
if unit["ai_behavior"] != "guard":
unit["ai_behavior"] = ""
if unit["ai_behavior"] == "guard" and not unit.has("ai_guard_radius"):
unit["ai_guard_radius"] = 2
unit["ai_guard_radius"] = max(0, int(unit.get("ai_guard_radius", 0)))
var guard_anchor := _condition_cell(unit.get("ai_guard_anchor", [unit["pos"].x, unit["pos"].y]))
if not is_inside(guard_anchor):
guard_anchor = unit["pos"]
unit["ai_guard_anchor"] = guard_anchor
unit["status_effects"] = []
unit["acted"] = false
unit["moved"] = false
@@ -3045,6 +3055,15 @@ func _run_enemy_turn() -> void:
func _enemy_take_action(enemy: Dictionary) -> void:
var movement_cells := _movement_range_for_unit(enemy)
if _is_ai_guard(enemy):
movement_cells = _ai_guard_movement_cells(enemy, movement_cells)
if not _ai_guard_is_engaged(enemy, movement_cells):
var return_cell := _best_ai_guard_return_cell(enemy, movement_cells)
if return_cell != enemy["pos"]:
_move_ai_unit(enemy, return_cell)
return
if _try_enemy_skill_action(enemy):
return
@@ -3054,16 +3073,16 @@ func _enemy_take_action(enemy: Dictionary) -> void:
return
var best_cell: Vector2i = enemy["pos"]
var move_attack_action := _best_ai_physical_attack_action(enemy, _movement_range_for_unit(enemy))
var move_attack_action := _best_ai_physical_attack_action(enemy, movement_cells)
if not move_attack_action.is_empty():
best_cell = move_attack_action["origin"]
else:
var target := _find_nearest_target(enemy, TEAM_PLAYER)
var target := _find_nearest_ai_guard_target(enemy, TEAM_PLAYER) if _is_ai_guard(enemy) else _find_nearest_target(enemy, TEAM_PLAYER)
if target.is_empty():
return
var best_distance := _manhattan(best_cell, target["pos"])
for cell in _movement_range_for_unit(enemy):
for cell in movement_cells:
var distance := _manhattan(cell, target["pos"])
if distance < best_distance:
best_distance = distance
@@ -3088,6 +3107,97 @@ func _enemy_take_action(enemy: Dictionary) -> void:
_resolve_combat(enemy, attack_action["target"])
func _move_ai_unit(unit: Dictionary, target_cell: Vector2i) -> bool:
if target_cell == unit["pos"]:
return true
var from_cell: Vector2i = unit["pos"]
unit["pos"] = target_cell
unit["moved"] = true
_face_unit_from_to(unit, from_cell, target_cell)
unit_motion_requested.emit(str(unit.get("id", "")), from_cell, target_cell)
_emit_log("%s, %s?쇰줈 ?뺣컯." % [unit["name"], _format_cell(target_cell)])
_run_events("unit_reaches_tile", str(unit.get("team", "")), turn_number, unit)
return battle_status == STATUS_ACTIVE
func _is_ai_guard(unit: Dictionary) -> bool:
return str(unit.get("ai_behavior", "")).strip_edges().to_lower() == "guard"
func _ai_guard_anchor(unit: Dictionary) -> Vector2i:
var anchor: Vector2i = unit.get("ai_guard_anchor", unit.get("pos", Vector2i(-1, -1)))
if not is_inside(anchor):
return unit.get("pos", Vector2i(-1, -1))
return anchor
func _ai_guard_radius(unit: Dictionary) -> int:
return max(0, int(unit.get("ai_guard_radius", 0)))
func _ai_guard_contains_cell(unit: Dictionary, cell: Vector2i) -> bool:
return _manhattan(_ai_guard_anchor(unit), cell) <= _ai_guard_radius(unit)
func _ai_guard_movement_cells(unit: Dictionary, movement_cells: Array[Vector2i]) -> Array[Vector2i]:
var filtered: Array[Vector2i] = []
for cell in movement_cells:
if _ai_guard_contains_cell(unit, cell):
filtered.append(cell)
if not filtered.is_empty():
return filtered
if not _ai_guard_contains_cell(unit, unit.get("pos", Vector2i(-1, -1))):
return movement_cells
return filtered
func _ai_guard_attack_origins(unit: Dictionary, movement_cells: Array[Vector2i]) -> Array[Vector2i]:
var origins: Array[Vector2i] = []
var current: Vector2i = unit.get("pos", Vector2i(-1, -1))
if is_inside(current):
origins.append(current)
for cell in movement_cells:
if not origins.has(cell):
origins.append(cell)
return origins
func _ai_guard_is_engaged(unit: Dictionary, movement_cells: Array[Vector2i]) -> bool:
var target_team := _opposing_team(str(unit.get("team", "")))
if target_team.is_empty():
return false
for target in get_living_units(target_team):
if _ai_guard_contains_cell(unit, target.get("pos", Vector2i(-1, -1))):
return true
return not _best_ai_physical_attack_action(unit, _ai_guard_attack_origins(unit, movement_cells)).is_empty()
func _best_ai_guard_return_cell(unit: Dictionary, movement_cells: Array[Vector2i]) -> Vector2i:
var best_cell: Vector2i = unit.get("pos", Vector2i(-1, -1))
var anchor := _ai_guard_anchor(unit)
var best_distance := _manhattan(best_cell, anchor)
for cell in movement_cells:
var distance := _manhattan(cell, anchor)
if distance < best_distance:
best_distance = distance
best_cell = cell
return best_cell
func _find_nearest_ai_guard_target(unit: Dictionary, target_team: String) -> Dictionary:
var best_target := {}
var best_score := 9999
for target in get_living_units(target_team):
if not _ai_guard_contains_cell(unit, target.get("pos", Vector2i(-1, -1))):
continue
var distance := _manhattan(unit["pos"], target["pos"])
var score := distance - int(target.get("ai_target_priority", 0))
if score < best_score:
best_score = score
best_target = target
return best_target
func _best_ai_physical_attack_action(attacker: Dictionary, origins: Array[Vector2i]) -> Dictionary:
var best_action := {}
var best_score := -999999