From f16d94a8f1572ec7a752728e85fa242581f6d2f0 Mon Sep 17 00:00:00 2001 From: autocommit Date: Thu, 16 Apr 2026 12:21:23 -0700 Subject: [PATCH] =?UTF-8?q?feat(ai):=20=E2=9C=A8=20Introduce=20emergency?= =?UTF-8?q?=20garrison=20logic=20for=20undefended=20cities=20in=20SimpleHe?= =?UTF-8?q?uristicAI=20to=20enhance=20tactical=20defense=20under=20threat?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Lilith Autocommit --- .../src/modules/ai/simple_heuristic_ai.gd | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/game/engine/src/modules/ai/simple_heuristic_ai.gd b/src/game/engine/src/modules/ai/simple_heuristic_ai.gd index 8af2fa38..19e0a718 100644 --- a/src/game/engine/src/modules/ai/simple_heuristic_ai.gd +++ b/src/game/engine/src/modules/ai/simple_heuristic_ai.gd @@ -96,6 +96,21 @@ static func process_player(player: RefCounted) -> Array: var bombard: Dictionary = _decide_city_bombard(ci, city, player) if not bombard.is_empty(): actions.append(bombard) + # Emergency garrison: undefended city with enemy within 10 hexes + # preempts the queue with a warrior (mirrors auto_play siege replace). + if mil_now == 0 and _enemy_within(city.position, 10, player.index): + var rush_id: String = _pick_buildable_military_unit_id(city, player) + var head_id: String = ( + str(city.production_queue[0].get("id", "")) + if not city.production_queue.is_empty() else "" + ) + if not rush_id.is_empty() and head_id != rush_id: + var udata: Dictionary = DataLoader.get_unit(rush_id) + city.production_queue = [{"type": "unit", "id": rush_id, + "cost": int(udata.get("cost", 0))}] + city.production_progress = 0 + if not rush_id.is_empty(): + continue if not city.production_queue.is_empty(): continue var prod: Dictionary = _decide_production(ci, player) @@ -224,6 +239,18 @@ static func _count_own_military_at( return total +static func _enemy_within( + pos: Vector2i, radius: int, own_idx: int +) -> bool: + var primary: Dictionary = GameState.get_primary_layer() + for u: Variant in primary.get("units", []): + if u == null or not u.is_alive() or int(u.get("owner")) == own_idx: + continue + if HexUtilsScript.hex_distance(pos, u.position) <= radius: + return true + return false + + static func _enemy_military_threat(player: RefCounted) -> Dictionary: ## count=in-range(<=8), total_count=all enemy combat. threatens_city @<=5. var count: int = 0