Improve AI air defense target prioritization.

Target the air defenses whose *threat ranges* come closest to friendly
bases rather than the closest sites themselves. In other words, the
SA-10 that is 5 miles behind the SA-6 will now be the priority.

This also treats EWRs a bit differently. If they are not protected by a
SAM their detection range will be used for determining their "threat"
range. Otherwise a heuristic is used to determine whether or not they
can be safely attacked without encroaching on the covering SAM.
This commit is contained in:
Dan Albert
2021-05-28 19:19:40 -07:00
parent cea264e871
commit e6b9a73d03
4 changed files with 51 additions and 15 deletions

View File

@@ -18,6 +18,7 @@ from typing import (
Tuple,
Type,
TypeVar,
Union,
)
from dcs.unittype import FlyingType
@@ -44,7 +45,7 @@ from game.theater.theatergroundobject import (
VehicleGroupGroundObject,
)
from game.transfers import CargoShip, Convoy
from game.utils import Distance, nautical_miles
from game.utils import Distance, nautical_miles, meters
from gen.ato import Package
from gen.flights.ai_flight_planner_db import aircraft_for_task
from gen.flights.closestairfields import (
@@ -296,23 +297,35 @@ class ObjectiveFinder:
self.game = game
self.is_player = is_player
def enemy_air_defenses(self) -> Iterator[TheaterGroundObject]:
def enemy_air_defenses(self) -> Iterator[tuple[TheaterGroundObject, Distance]]:
"""Iterates over all enemy SAM sites."""
doctrine = self.game.faction_for(self.is_player).doctrine
threat_zones = self.game.threat_zone_for(not self.is_player)
for cp in self.enemy_control_points():
for ground_object in cp.ground_objects:
is_ewr = isinstance(ground_object, EwrGroundObject)
is_sam = isinstance(ground_object, SamGroundObject)
if not is_ewr and not is_sam:
continue
if ground_object.is_dead:
continue
# TODO: Yield in order of most threatening.
# Need to sort in order of how close their defensive range comes
# to friendly assets. To do that we need to add effective range
# information to the database.
yield ground_object
if isinstance(ground_object, EwrGroundObject):
if threat_zones.threatened_by_air_defense(ground_object):
# This is a very weak heuristic for determining whether the EWR
# is close enough to be worth targeting before a SAM that is
# covering it. Ingress distance corresponds to the beginning of
# the attack range and is sufficient for most standoff weapons,
# so treating the ingress distance as the threat distance sorts
# these EWRs such that they will be attacked before SAMs that do
# not threaten the ingress point, but after those that do.
target_range = doctrine.ingress_egress_distance
else:
# But if the EWR isn't covered then we should only be worrying
# about its detection range.
target_range = ground_object.max_detection_range()
elif isinstance(ground_object, SamGroundObject):
target_range = ground_object.max_threat_range()
else:
continue
yield ground_object, target_range
def threatening_air_defenses(self) -> Iterator[TheaterGroundObject]:
"""Iterates over enemy SAMs in threat range of friendly control points.
@@ -320,7 +333,17 @@ class ObjectiveFinder:
SAM sites are sorted by their closest proximity to any friendly control
point (airfield or fleet).
"""
return self._targets_by_range(self.enemy_air_defenses())
target_ranges: list[tuple[TheaterGroundObject, Distance]] = []
for target, threat_range in self.enemy_air_defenses():
ranges: list[Distance] = []
for cp in self.friendly_control_points():
ranges.append(meters(target.distance_to(cp)) - threat_range)
target_ranges.append((target, min(ranges)))
target_ranges = sorted(target_ranges, key=operator.itemgetter(1))
for target, _range in target_ranges:
yield target
def enemy_vehicle_groups(self) -> Iterator[VehicleGroupGroundObject]:
"""Iterates over all enemy vehicle groups."""