Ignore non-escorted regions when planning escorts.

We shouldn't consider the non-escorted parts of the flight path when
checking for threats to determine if escorts should be used or not,
since escorts can't help in those areas anyway. This was causing escorts
to be overly requested since the bullseye is now a part of the
"flight plan", but could have also triggered for divert waypoints, or
for aircraft taking off in a retreat from a threatened location.
This commit is contained in:
Dan Albert 2021-05-23 12:58:16 -07:00
parent ddd6e7d18f
commit eedb5c26a9
3 changed files with 36 additions and 8 deletions

View File

@ -1,7 +1,7 @@
from __future__ import annotations from __future__ import annotations
from functools import singledispatchmethod from functools import singledispatchmethod
from typing import Optional, TYPE_CHECKING, Union from typing import Optional, TYPE_CHECKING, Union, Iterable
from dcs.mapping import Point as DcsPoint from dcs.mapping import Point as DcsPoint
from shapely.geometry import ( from shapely.geometry import (
@ -16,7 +16,7 @@ from shapely.ops import nearest_points, unary_union
from game.theater import ControlPoint from game.theater import ControlPoint
from game.utils import Distance, meters, nautical_miles from game.utils import Distance, meters, nautical_miles
from gen.flights.closestairfields import ObjectiveDistanceCache from gen.flights.closestairfields import ObjectiveDistanceCache
from gen.flights.flight import Flight from gen.flights.flight import Flight, FlightWaypoint
if TYPE_CHECKING: if TYPE_CHECKING:
from game import Game from game import Game
@ -71,6 +71,13 @@ class ThreatZones:
LineString((self.dcs_to_shapely_point(p.position) for p in flight.points)) LineString((self.dcs_to_shapely_point(p.position) for p in flight.points))
) )
def waypoints_threatened_by_aircraft(
self, waypoints: Iterable[FlightWaypoint]
) -> bool:
return self.threatened_by_aircraft(
LineString((self.dcs_to_shapely_point(p.position) for p in waypoints))
)
@singledispatchmethod @singledispatchmethod
def threatened_by_air_defense(self, target) -> bool: def threatened_by_air_defense(self, target) -> bool:
raise NotImplementedError raise NotImplementedError
@ -99,6 +106,13 @@ class ThreatZones:
LineString((self.dcs_to_shapely_point(p.position) for p in flight.points)) LineString((self.dcs_to_shapely_point(p.position) for p in flight.points))
) )
def waypoints_threatened_by_radar_sam(
self, waypoints: Iterable[FlightWaypoint]
) -> bool:
return self.threatened_by_radar_sam(
LineString((self.dcs_to_shapely_point(p.position) for p in waypoints))
)
@classmethod @classmethod
def closest_enemy_airbase( def closest_enemy_airbase(
cls, location: ControlPoint, max_distance: Distance cls, location: ControlPoint, max_distance: Distance

View File

@ -831,9 +831,13 @@ class CoalitionMissionPlanner:
def check_needed_escorts(self, builder: PackageBuilder) -> Dict[EscortType, bool]: def check_needed_escorts(self, builder: PackageBuilder) -> Dict[EscortType, bool]:
threats = defaultdict(bool) threats = defaultdict(bool)
for flight in builder.package.flights: for flight in builder.package.flights:
if self.threat_zones.threatened_by_aircraft(flight): if self.threat_zones.waypoints_threatened_by_aircraft(
flight.flight_plan.escorted_waypoints()
):
threats[EscortType.AirToAir] = True threats[EscortType.AirToAir] = True
if self.threat_zones.threatened_by_radar_sam(flight): if self.threat_zones.waypoints_threatened_by_radar_sam(
list(flight.flight_plan.escorted_waypoints())
):
threats[EscortType.Sead] = True threats[EscortType.Sead] = True
return threats return threats

View File

@ -198,6 +198,20 @@ class FlightPlan:
def dismiss_escort_at(self) -> Optional[FlightWaypoint]: def dismiss_escort_at(self) -> Optional[FlightWaypoint]:
return None return None
def escorted_waypoints(self) -> Iterator[FlightWaypoint]:
begin = self.request_escort_at()
end = self.dismiss_escort_at()
if begin is None or end is None:
return
escorting = False
for waypoint in self.waypoints:
if waypoint == begin:
escorting = True
if escorting:
yield waypoint
if waypoint == end:
return
def takeoff_time(self) -> Optional[timedelta]: def takeoff_time(self) -> Optional[timedelta]:
tot_waypoint = self.tot_waypoint tot_waypoint = self.tot_waypoint
if tot_waypoint is None: if tot_waypoint is None:
@ -600,10 +614,6 @@ class StrikeFlightPlan(FormationFlightPlan):
) )
return total return total
@property
def mission_speed(self) -> Speed:
return GroundSpeed.for_flight(self.flight, self.ingress.alt)
@property @property
def join_time(self) -> timedelta: def join_time(self) -> timedelta:
travel_time = self.travel_time_between_waypoints(self.join, self.ingress) travel_time = self.travel_time_between_waypoints(self.join, self.ingress)