Add sentry pacing to opening battle

This commit is contained in:
2026-06-20 07:06:47 +09:00
parent 345ed20e4a
commit c26b052c90
7 changed files with 277 additions and 12 deletions

View File

@@ -607,7 +607,7 @@ func _prepare_unit(source_unit: Dictionary) -> Dictionary:
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":
if not ["guard", "sentry"].has(unit["ai_behavior"]):
unit["ai_behavior"] = ""
if unit["ai_behavior"] == "guard" and not unit.has("ai_guard_radius"):
unit["ai_guard_radius"] = 2
@@ -616,6 +616,15 @@ func _prepare_unit(source_unit: Dictionary) -> Dictionary:
if not is_inside(guard_anchor):
guard_anchor = unit["pos"]
unit["ai_guard_anchor"] = guard_anchor
if unit["ai_behavior"] == "sentry" and not unit.has("ai_sentry_radius"):
unit["ai_sentry_radius"] = 4
unit["ai_sentry_radius"] = max(0, int(unit.get("ai_sentry_radius", 0)))
unit["ai_sentry_activation_turn"] = max(0, int(unit.get("ai_sentry_activation_turn", 0)))
var sentry_anchor := _condition_cell(unit.get("ai_sentry_anchor", [unit["pos"].x, unit["pos"].y]))
if not is_inside(sentry_anchor):
sentry_anchor = unit["pos"]
unit["ai_sentry_anchor"] = sentry_anchor
unit["ai_awake"] = bool(unit.get("ai_awake", false))
unit["status_effects"] = []
unit["acted"] = false
unit["moved"] = false
@@ -3117,6 +3126,11 @@ func _run_enemy_turn() -> void:
func _enemy_take_action(enemy: Dictionary) -> void:
var movement_cells := _movement_range_for_unit(enemy)
if _is_ai_sentry(enemy) and not _ai_sentry_is_awake(enemy, movement_cells):
var sentry_return_cell := _best_ai_sentry_return_cell(enemy, movement_cells)
if sentry_return_cell != enemy["pos"]:
_move_ai_unit(enemy, sentry_return_cell)
return
if _is_ai_guard(enemy):
movement_cells = _ai_guard_movement_cells(enemy, movement_cells)
if not _ai_guard_is_engaged(enemy, movement_cells):
@@ -3185,6 +3199,60 @@ func _is_ai_guard(unit: Dictionary) -> bool:
return str(unit.get("ai_behavior", "")).strip_edges().to_lower() == "guard"
func _is_ai_sentry(unit: Dictionary) -> bool:
return str(unit.get("ai_behavior", "")).strip_edges().to_lower() == "sentry"
func _ai_sentry_anchor(unit: Dictionary) -> Vector2i:
var anchor: Vector2i = unit.get("ai_sentry_anchor", unit.get("pos", Vector2i(-1, -1)))
if not is_inside(anchor):
return unit.get("pos", Vector2i(-1, -1))
return anchor
func _ai_sentry_radius(unit: Dictionary) -> int:
return max(0, int(unit.get("ai_sentry_radius", 0)))
func _ai_sentry_contains_cell(unit: Dictionary, cell: Vector2i) -> bool:
return _manhattan(_ai_sentry_anchor(unit), cell) <= _ai_sentry_radius(unit)
func _ai_sentry_is_awake(unit: Dictionary, movement_cells: Array[Vector2i]) -> bool:
if bool(unit.get("ai_awake", false)):
return true
var activation_turn := int(unit.get("ai_sentry_activation_turn", 0))
if activation_turn > 0 and turn_number >= activation_turn:
unit["ai_awake"] = true
return true
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_sentry_contains_cell(unit, target.get("pos", Vector2i(-1, -1))):
unit["ai_awake"] = true
return true
if not _best_ai_physical_attack_action(unit, [unit.get("pos", Vector2i(-1, -1))]).is_empty():
unit["ai_awake"] = true
return true
if not movement_cells.is_empty() and not _best_ai_physical_attack_action(unit, movement_cells).is_empty():
unit["ai_awake"] = true
return true
return false
func _best_ai_sentry_return_cell(unit: Dictionary, movement_cells: Array[Vector2i]) -> Vector2i:
var best_cell: Vector2i = unit.get("pos", Vector2i(-1, -1))
var anchor := _ai_sentry_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 _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):

View File

@@ -63,6 +63,18 @@ func hydrate_deployment(deployment: Dictionary) -> Dictionary:
unit["ai_guard_radius"] = int(deployment.get("ai_guard_radius", 0))
elif unit["ai_behavior"] == "guard":
unit["ai_guard_radius"] = 2
if deployment.has("ai_sentry_anchor"):
unit["ai_sentry_anchor"] = deployment["ai_sentry_anchor"]
elif unit["ai_behavior"] == "sentry":
unit["ai_sentry_anchor"] = deployment.get("pos", [0, 0])
if deployment.has("ai_sentry_radius"):
unit["ai_sentry_radius"] = int(deployment.get("ai_sentry_radius", 0))
elif unit["ai_behavior"] == "sentry":
unit["ai_sentry_radius"] = 4
if deployment.has("ai_sentry_activation_turn"):
unit["ai_sentry_activation_turn"] = int(deployment.get("ai_sentry_activation_turn", 0))
if deployment.has("ai_awake"):
unit["ai_awake"] = bool(deployment.get("ai_awake", false))
unit["level"] = int(deployment.get("level", officer.get("level", 1)))
unit["exp"] = int(deployment.get("exp", officer.get("exp", 0)))
unit["max_hp"] = int(stats.get("hp", stats.get("max_hp", 1)))