Improve DEAD mission planning.

We don't need to include a SEAD flight in missions against EWRs or SAMs
that no longer have a radar.

Also plan DEAD missions against air defenses that have no radars.
Previously we would never finish killing launcher only sites (which
cannot defend any more, but are cheaper to return to working order than
a fully destroyed site) nor would we plan DEAD against IR SAMs or AAA.
This commit is contained in:
Dan Albert 2021-05-22 14:56:18 -07:00
parent 3a08944c99
commit 57fe5c04ec
2 changed files with 25 additions and 29 deletions

View File

@ -137,7 +137,7 @@ class TheaterGroundObject(MissionTarget):
return False return False
@property @property
def has_radar(self) -> bool: def has_alive_radar(self) -> bool:
"""Returns True if the ground object contains a unit with radar.""" """Returns True if the ground object contains a unit with radar."""
for group in self.groups: for group in self.groups:
for unit in group.units: for unit in group.units:

View File

@ -17,6 +17,7 @@ from typing import (
TYPE_CHECKING, TYPE_CHECKING,
Tuple, Tuple,
Type, Type,
TypeVar,
) )
from dcs.unittype import FlyingType from dcs.unittype import FlyingType
@ -41,7 +42,6 @@ from game.theater.theatergroundobject import (
) )
from game.transfers import CargoShip, Convoy from game.transfers import CargoShip, Convoy
from game.utils import Distance, nautical_miles from game.utils import Distance, nautical_miles
from gen import Conflict
from gen.ato import Package from gen.ato import Package
from gen.flights.ai_flight_planner_db import aircraft_for_task from gen.flights.ai_flight_planner_db import aircraft_for_task
from gen.flights.closestairfields import ( from gen.flights.closestairfields import (
@ -253,6 +253,9 @@ class PackageBuilder:
self.package.remove_flight(flight) self.package.remove_flight(flight)
MissionTargetType = TypeVar("MissionTargetType", bound=MissionTarget)
class ObjectiveFinder: class ObjectiveFinder:
"""Identifies potential objectives for the mission planner.""" """Identifies potential objectives for the mission planner."""
@ -264,11 +267,8 @@ class ObjectiveFinder:
self.game = game self.game = game
self.is_player = is_player self.is_player = is_player
def enemy_sams(self) -> Iterator[TheaterGroundObject]: def enemy_air_defenses(self) -> Iterator[TheaterGroundObject]:
"""Iterates over all enemy SAM sites.""" """Iterates over all enemy SAM sites."""
# Control points might have the same ground object several times, for
# some reason.
found_targets: Set[str] = set()
for cp in self.enemy_control_points(): for cp in self.enemy_control_points():
for ground_object in cp.ground_objects: for ground_object in cp.ground_objects:
is_ewr = isinstance(ground_object, EwrGroundObject) is_ewr = isinstance(ground_object, EwrGroundObject)
@ -279,26 +279,19 @@ class ObjectiveFinder:
if ground_object.is_dead: if ground_object.is_dead:
continue continue
if ground_object.name in found_targets:
continue
if not ground_object.has_radar:
continue
# TODO: Yield in order of most threatening. # TODO: Yield in order of most threatening.
# Need to sort in order of how close their defensive range comes # Need to sort in order of how close their defensive range comes
# to friendly assets. To do that we need to add effective range # to friendly assets. To do that we need to add effective range
# information to the database. # information to the database.
yield ground_object yield ground_object
found_targets.add(ground_object.name)
def threatening_sams(self) -> Iterator[MissionTarget]: def threatening_air_defenses(self) -> Iterator[TheaterGroundObject]:
"""Iterates over enemy SAMs in threat range of friendly control points. """Iterates over enemy SAMs in threat range of friendly control points.
SAM sites are sorted by their closest proximity to any friendly control SAM sites are sorted by their closest proximity to any friendly control
point (airfield or fleet). point (airfield or fleet).
""" """
return self._targets_by_range(self.enemy_sams()) return self._targets_by_range(self.enemy_air_defenses())
def enemy_vehicle_groups(self) -> Iterator[VehicleGroupGroundObject]: def enemy_vehicle_groups(self) -> Iterator[VehicleGroupGroundObject]:
"""Iterates over all enemy vehicle groups.""" """Iterates over all enemy vehicle groups."""
@ -340,9 +333,9 @@ class ObjectiveFinder:
return self._targets_by_range(self.enemy_ships()) return self._targets_by_range(self.enemy_ships())
def _targets_by_range( def _targets_by_range(
self, targets: Iterable[MissionTarget] self, targets: Iterable[MissionTargetType]
) -> Iterator[MissionTarget]: ) -> Iterator[MissionTargetType]:
target_ranges: List[Tuple[MissionTarget, int]] = [] target_ranges: List[Tuple[MissionTargetType, int]] = []
for target in targets: for target in targets:
ranges: List[int] = [] ranges: List[int] = []
for cp in self.friendly_control_points(): for cp in self.friendly_control_points():
@ -630,18 +623,21 @@ class CoalitionMissionPlanner:
# or objects, plan DEAD. # or objects, plan DEAD.
# Find enemy SAM sites with ranges that extend to within 50 nmi of # Find enemy SAM sites with ranges that extend to within 50 nmi of
# friendly CPs, front, lines, or objects, plan DEAD. # friendly CPs, front, lines, or objects, plan DEAD.
for sam in self.objective_finder.threatening_sams(): for sam in self.objective_finder.threatening_air_defenses():
yield ProposedMission( flights = [ProposedFlight(FlightType.DEAD, 2, self.MAX_SEAD_RANGE)]
sam,
[ # Only include SEAD against SAMs that still have emitters. No need to
ProposedFlight(FlightType.DEAD, 2, self.MAX_SEAD_RANGE), # suppress an EWR, and SEAD isn't useful against a SAM that no longer has a
ProposedFlight(FlightType.SEAD, 2, self.MAX_SEAD_RANGE), # radar.
# TODO: Max escort range. if isinstance(sam, SamGroundObject) and sam.has_alive_radar:
ProposedFlight( flights.append(ProposedFlight(FlightType.SEAD, 2, self.MAX_SEAD_RANGE))
FlightType.ESCORT, 2, self.MAX_SEAD_RANGE, EscortType.AirToAir # TODO: Max escort range.
), flights.append(
], ProposedFlight(
FlightType.ESCORT, 2, self.MAX_SEAD_RANGE, EscortType.AirToAir
)
) )
yield ProposedMission(sam, flights)
# These will only rarely get planned. When a convoy is travelling multiple legs, # These will only rarely get planned. When a convoy is travelling multiple legs,
# they're targetable after the first leg. The reason for this is that # they're targetable after the first leg. The reason for this is that