Improve hold/split/join point positioning.

This also removes ascend/descend waypoints. They don't seem to be
helping at all. The AI already gets an implicit ascend waypoint (they
won't go to waypoint one until they've climbed sufficiently), and
forcing unnecessary sharp turns toward the possibly mispredicted ascent
direction can mess with the AI. It's also yet another variable to
contend with when planning hold points, and hold points do essentially
the same thing.

Fixes https://github.com/Khopa/dcs_liberation/issues/352.
This commit is contained in:
Dan Albert 2020-11-15 18:28:40 -08:00
parent dfc31dfd5c
commit 21cd764f66
3 changed files with 74 additions and 31 deletions

View File

@ -4,6 +4,9 @@
* **[Factions]** Added factions : Georgia 2008, USN 1985, France 2005 Frenchpack by HerrTom * **[Factions]** Added factions : Georgia 2008, USN 1985, France 2005 Frenchpack by HerrTom
* **[Factions]** Added map Persian Gulf full by Plob * **[Factions]** Added map Persian Gulf full by Plob
## Fixes :
* **[Flight Planner]** Hold, join, and split points are planned cautiously near enemy airfields. Ascend/descend points are no longer planned.
# 2.2.0 # 2.2.0
## Features/Improvements : ## Features/Improvements :

View File

@ -16,6 +16,8 @@ class Doctrine:
sead_max_range: int sead_max_range: int
rendezvous_altitude: int rendezvous_altitude: int
hold_distance: int
push_distance: int
join_distance: int join_distance: int
split_distance: int split_distance: int
ingress_egress_distance: int ingress_egress_distance: int
@ -44,6 +46,8 @@ MODERN_DOCTRINE = Doctrine(
strike_max_range=1500000, strike_max_range=1500000,
sead_max_range=1500000, sead_max_range=1500000,
rendezvous_altitude=feet_to_meter(25000), rendezvous_altitude=feet_to_meter(25000),
hold_distance=nm_to_meter(15),
push_distance=nm_to_meter(20),
join_distance=nm_to_meter(20), join_distance=nm_to_meter(20),
split_distance=nm_to_meter(20), split_distance=nm_to_meter(20),
ingress_egress_distance=nm_to_meter(45), ingress_egress_distance=nm_to_meter(45),
@ -69,6 +73,8 @@ COLDWAR_DOCTRINE = Doctrine(
strike_max_range=1500000, strike_max_range=1500000,
sead_max_range=1500000, sead_max_range=1500000,
rendezvous_altitude=feet_to_meter(22000), rendezvous_altitude=feet_to_meter(22000),
hold_distance=nm_to_meter(10),
push_distance=nm_to_meter(10),
join_distance=nm_to_meter(10), join_distance=nm_to_meter(10),
split_distance=nm_to_meter(10), split_distance=nm_to_meter(10),
ingress_egress_distance=nm_to_meter(30), ingress_egress_distance=nm_to_meter(30),
@ -93,6 +99,8 @@ WWII_DOCTRINE = Doctrine(
antiship=True, antiship=True,
strike_max_range=1500000, strike_max_range=1500000,
sead_max_range=1500000, sead_max_range=1500000,
hold_distance=nm_to_meter(5),
push_distance=nm_to_meter(5),
join_distance=nm_to_meter(5), join_distance=nm_to_meter(5),
split_distance=nm_to_meter(5), split_distance=nm_to_meter(5),
rendezvous_altitude=feet_to_meter(10000), rendezvous_altitude=feet_to_meter(10000),

View File

@ -7,6 +7,7 @@ generating the waypoints for the mission.
""" """
from __future__ import annotations from __future__ import annotations
import math
from datetime import timedelta from datetime import timedelta
from functools import cached_property from functools import cached_property
import logging import logging
@ -275,18 +276,14 @@ class PatrollingFlightPlan(FlightPlan):
@dataclass(frozen=True) @dataclass(frozen=True)
class BarCapFlightPlan(PatrollingFlightPlan): class BarCapFlightPlan(PatrollingFlightPlan):
takeoff: FlightWaypoint takeoff: FlightWaypoint
ascent: FlightWaypoint
descent: FlightWaypoint
land: FlightWaypoint land: FlightWaypoint
@property @property
def waypoints(self) -> List[FlightWaypoint]: def waypoints(self) -> List[FlightWaypoint]:
return [ return [
self.takeoff, self.takeoff,
self.ascent,
self.patrol_start, self.patrol_start,
self.patrol_end, self.patrol_end,
self.descent,
self.land, self.land,
] ]
@ -294,20 +291,16 @@ class BarCapFlightPlan(PatrollingFlightPlan):
@dataclass(frozen=True) @dataclass(frozen=True)
class CasFlightPlan(PatrollingFlightPlan): class CasFlightPlan(PatrollingFlightPlan):
takeoff: FlightWaypoint takeoff: FlightWaypoint
ascent: FlightWaypoint
target: FlightWaypoint target: FlightWaypoint
descent: FlightWaypoint
land: FlightWaypoint land: FlightWaypoint
@property @property
def waypoints(self) -> List[FlightWaypoint]: def waypoints(self) -> List[FlightWaypoint]:
return [ return [
self.takeoff, self.takeoff,
self.ascent,
self.patrol_start, self.patrol_start,
self.target, self.target,
self.patrol_end, self.patrol_end,
self.descent,
self.land, self.land,
] ]
@ -321,18 +314,14 @@ class CasFlightPlan(PatrollingFlightPlan):
@dataclass(frozen=True) @dataclass(frozen=True)
class FrontLineCapFlightPlan(PatrollingFlightPlan): class FrontLineCapFlightPlan(PatrollingFlightPlan):
takeoff: FlightWaypoint takeoff: FlightWaypoint
ascent: FlightWaypoint
descent: FlightWaypoint
land: FlightWaypoint land: FlightWaypoint
@property @property
def waypoints(self) -> List[FlightWaypoint]: def waypoints(self) -> List[FlightWaypoint]:
return [ return [
self.takeoff, self.takeoff,
self.ascent,
self.patrol_start, self.patrol_start,
self.patrol_end, self.patrol_end,
self.descent,
self.land, self.land,
] ]
@ -360,28 +349,24 @@ class FrontLineCapFlightPlan(PatrollingFlightPlan):
@dataclass(frozen=True) @dataclass(frozen=True)
class StrikeFlightPlan(FormationFlightPlan): class StrikeFlightPlan(FormationFlightPlan):
takeoff: FlightWaypoint takeoff: FlightWaypoint
ascent: FlightWaypoint
hold: FlightWaypoint hold: FlightWaypoint
join: FlightWaypoint join: FlightWaypoint
ingress: FlightWaypoint ingress: FlightWaypoint
targets: List[FlightWaypoint] targets: List[FlightWaypoint]
egress: FlightWaypoint egress: FlightWaypoint
split: FlightWaypoint split: FlightWaypoint
descent: FlightWaypoint
land: FlightWaypoint land: FlightWaypoint
@property @property
def waypoints(self) -> List[FlightWaypoint]: def waypoints(self) -> List[FlightWaypoint]:
return [ return [
self.takeoff, self.takeoff,
self.ascent,
self.hold, self.hold,
self.join, self.join,
self.ingress self.ingress
] + self.targets + [ ] + self.targets + [
self.egress, self.egress,
self.split, self.split,
self.descent,
self.land, self.land,
] ]
@ -681,10 +666,8 @@ class FlightPlanBuilder:
flight=flight, flight=flight,
patrol_duration=self.doctrine.cap_duration, patrol_duration=self.doctrine.cap_duration,
takeoff=builder.takeoff(flight.from_cp), takeoff=builder.takeoff(flight.from_cp),
ascent=builder.ascent(flight.from_cp),
patrol_start=start, patrol_start=start,
patrol_end=end, patrol_end=end,
descent=descent,
land=land land=land
) )
@ -736,10 +719,8 @@ class FlightPlanBuilder:
# duration of the escorted mission, or until it is winchester/bingo. # duration of the escorted mission, or until it is winchester/bingo.
patrol_duration=self.doctrine.cap_duration, patrol_duration=self.doctrine.cap_duration,
takeoff=builder.takeoff(flight.from_cp), takeoff=builder.takeoff(flight.from_cp),
ascent=builder.ascent(flight.from_cp),
patrol_start=start, patrol_start=start,
patrol_end=end, patrol_end=end,
descent=descent,
land=land land=land
) )
@ -805,14 +786,12 @@ class FlightPlanBuilder:
package=self.package, package=self.package,
flight=flight, flight=flight,
takeoff=builder.takeoff(flight.from_cp), takeoff=builder.takeoff(flight.from_cp),
ascent=builder.ascent(flight.from_cp),
hold=builder.hold(self._hold_point(flight)), hold=builder.hold(self._hold_point(flight)),
join=builder.join(self.package.waypoints.join), join=builder.join(self.package.waypoints.join),
ingress=ingress, ingress=ingress,
targets=[target], targets=[target],
egress=egress, egress=egress,
split=builder.split(self.package.waypoints.split), split=builder.split(self.package.waypoints.split),
descent=descent,
land=land land=land
) )
@ -842,11 +821,9 @@ class FlightPlanBuilder:
flight=flight, flight=flight,
patrol_duration=self.doctrine.cas_duration, patrol_duration=self.doctrine.cas_duration,
takeoff=builder.takeoff(flight.from_cp), takeoff=builder.takeoff(flight.from_cp),
ascent=builder.ascent(flight.from_cp),
patrol_start=builder.ingress_cas(ingress, location), patrol_start=builder.ingress_cas(ingress, location),
target=builder.cas(center), target=builder.cas(center),
patrol_end=builder.egress(egress, location), patrol_end=builder.egress(egress, location),
descent=descent,
land=land land=land
) )
@ -871,12 +848,50 @@ class FlightPlanBuilder:
return builder.strike_area(location) return builder.strike_area(location)
def _hold_point(self, flight: Flight) -> Point: def _hold_point(self, flight: Flight) -> Point:
heading = flight.from_cp.position.heading_between_point( assert self.package.waypoints is not None
self.package.target.position origin = flight.from_cp.position
) target = self.package.target.position
return flight.from_cp.position.point_from_heading( join = self.package.waypoints.join
heading, nm_to_meter(15) origin_to_target = origin.distance_to_point(target)
join_to_target = join.distance_to_point(target)
if origin_to_target < join_to_target:
# If the origin airfield is closer to the target than the join
# point, plan the hold point such that it retreats from the origin
# airfield.
return join.point_from_heading(target.heading_between_point(origin),
self.doctrine.push_distance)
heading_to_join = origin.heading_between_point(join)
hold_point = origin.point_from_heading(heading_to_join,
self.doctrine.push_distance)
if hold_point.distance_to_point(join) >= self.doctrine.push_distance:
# Hold point is between the origin airfield and the join point and
# spaced sufficiently.
return hold_point
# The hold point is between the origin airfield and the join point, but
# the distance between the hold point and the join point is too short.
# Bend the hold point out to extend the distance while maintaining the
# minimum distance from the origin airfield to keep the AI flying
# properly.
origin_to_join = origin.distance_to_point(join)
cos_theta = (
(self.doctrine.hold_distance ** 2 +
origin_to_join ** 2 -
self.doctrine.join_distance ** 2) /
(2 * self.doctrine.hold_distance * origin_to_join)
) )
try:
theta = math.acos(cos_theta)
except ValueError:
# No solution that maintains hold and join distances. Extend the
# hold point away from the target.
return origin.point_from_heading(
target.heading_between_point(origin),
self.doctrine.hold_distance)
return origin.point_from_heading(heading_to_join - theta,
self.doctrine.hold_distance)
# TODO: Make a model for the waypoint builder and use that in the UI. # TODO: Make a model for the waypoint builder and use that in the UI.
def generate_ascend_point(self, flight: Flight, def generate_ascend_point(self, flight: Flight,
@ -944,23 +959,37 @@ class FlightPlanBuilder:
package=self.package, package=self.package,
flight=flight, flight=flight,
takeoff=builder.takeoff(flight.from_cp), takeoff=builder.takeoff(flight.from_cp),
ascent=builder.ascent(flight.from_cp),
hold=builder.hold(self._hold_point(flight)), hold=builder.hold(self._hold_point(flight)),
join=builder.join(self.package.waypoints.join), join=builder.join(self.package.waypoints.join),
ingress=ingress, ingress=ingress,
targets=target_waypoints, targets=target_waypoints,
egress=builder.egress(self.package.waypoints.egress, location), egress=builder.egress(self.package.waypoints.egress, location),
split=builder.split(self.package.waypoints.split), split=builder.split(self.package.waypoints.split),
descent=descent,
land=land land=land
) )
def _join_point(self, ingress_point: Point) -> Point: def _join_point(self, ingress_point: Point) -> Point:
ingress_distance = self._distance_to_package_airfield(ingress_point)
if ingress_distance < self.doctrine.join_distance:
# If the ingress point is close to the origin, plan the join point
# farther back.
return ingress_point.point_from_heading(
self.package.target.position.heading_between_point(
self.package_airfield().position),
self.doctrine.join_distance)
heading = self._heading_to_package_airfield(ingress_point) heading = self._heading_to_package_airfield(ingress_point)
return ingress_point.point_from_heading(heading, return ingress_point.point_from_heading(heading,
-self.doctrine.join_distance) -self.doctrine.join_distance)
def _split_point(self, egress_point: Point) -> Point: def _split_point(self, egress_point: Point) -> Point:
egress_distance = self._distance_to_package_airfield(egress_point)
if egress_distance < self.doctrine.split_distance:
# If the ingress point is close to the origin, plan the split point
# farther back.
return egress_point.point_from_heading(
self.package.target.position.heading_between_point(
self.package_airfield().position),
self.doctrine.split_distance)
heading = self._heading_to_package_airfield(egress_point) heading = self._heading_to_package_airfield(egress_point)
return egress_point.point_from_heading(heading, return egress_point.point_from_heading(heading,
-self.doctrine.split_distance) -self.doctrine.split_distance)
@ -983,6 +1012,9 @@ class FlightPlanBuilder:
def _heading_to_package_airfield(self, point: Point) -> int: def _heading_to_package_airfield(self, point: Point) -> int:
return self.package_airfield().position.heading_between_point(point) return self.package_airfield().position.heading_between_point(point)
def _distance_to_package_airfield(self, point: Point) -> int:
return self.package_airfield().position.distance_to_point(point)
def package_airfield(self) -> ControlPoint: def package_airfield(self) -> ControlPoint:
# We'll always have a package, but if this is being planned via the UI # We'll always have a package, but if this is being planned via the UI
# it could be the first flight in the package. # it could be the first flight in the package.