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 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
## Features/Improvements :

View File

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

View File

@ -7,6 +7,7 @@ generating the waypoints for the mission.
"""
from __future__ import annotations
import math
from datetime import timedelta
from functools import cached_property
import logging
@ -275,18 +276,14 @@ class PatrollingFlightPlan(FlightPlan):
@dataclass(frozen=True)
class BarCapFlightPlan(PatrollingFlightPlan):
takeoff: FlightWaypoint
ascent: FlightWaypoint
descent: FlightWaypoint
land: FlightWaypoint
@property
def waypoints(self) -> List[FlightWaypoint]:
return [
self.takeoff,
self.ascent,
self.patrol_start,
self.patrol_end,
self.descent,
self.land,
]
@ -294,20 +291,16 @@ class BarCapFlightPlan(PatrollingFlightPlan):
@dataclass(frozen=True)
class CasFlightPlan(PatrollingFlightPlan):
takeoff: FlightWaypoint
ascent: FlightWaypoint
target: FlightWaypoint
descent: FlightWaypoint
land: FlightWaypoint
@property
def waypoints(self) -> List[FlightWaypoint]:
return [
self.takeoff,
self.ascent,
self.patrol_start,
self.target,
self.patrol_end,
self.descent,
self.land,
]
@ -321,18 +314,14 @@ class CasFlightPlan(PatrollingFlightPlan):
@dataclass(frozen=True)
class FrontLineCapFlightPlan(PatrollingFlightPlan):
takeoff: FlightWaypoint
ascent: FlightWaypoint
descent: FlightWaypoint
land: FlightWaypoint
@property
def waypoints(self) -> List[FlightWaypoint]:
return [
self.takeoff,
self.ascent,
self.patrol_start,
self.patrol_end,
self.descent,
self.land,
]
@ -360,28 +349,24 @@ class FrontLineCapFlightPlan(PatrollingFlightPlan):
@dataclass(frozen=True)
class StrikeFlightPlan(FormationFlightPlan):
takeoff: FlightWaypoint
ascent: FlightWaypoint
hold: FlightWaypoint
join: FlightWaypoint
ingress: FlightWaypoint
targets: List[FlightWaypoint]
egress: FlightWaypoint
split: FlightWaypoint
descent: FlightWaypoint
land: FlightWaypoint
@property
def waypoints(self) -> List[FlightWaypoint]:
return [
self.takeoff,
self.ascent,
self.hold,
self.join,
self.ingress
] + self.targets + [
self.egress,
self.split,
self.descent,
self.land,
]
@ -681,10 +666,8 @@ class FlightPlanBuilder:
flight=flight,
patrol_duration=self.doctrine.cap_duration,
takeoff=builder.takeoff(flight.from_cp),
ascent=builder.ascent(flight.from_cp),
patrol_start=start,
patrol_end=end,
descent=descent,
land=land
)
@ -736,10 +719,8 @@ class FlightPlanBuilder:
# duration of the escorted mission, or until it is winchester/bingo.
patrol_duration=self.doctrine.cap_duration,
takeoff=builder.takeoff(flight.from_cp),
ascent=builder.ascent(flight.from_cp),
patrol_start=start,
patrol_end=end,
descent=descent,
land=land
)
@ -805,14 +786,12 @@ class FlightPlanBuilder:
package=self.package,
flight=flight,
takeoff=builder.takeoff(flight.from_cp),
ascent=builder.ascent(flight.from_cp),
hold=builder.hold(self._hold_point(flight)),
join=builder.join(self.package.waypoints.join),
ingress=ingress,
targets=[target],
egress=egress,
split=builder.split(self.package.waypoints.split),
descent=descent,
land=land
)
@ -842,11 +821,9 @@ class FlightPlanBuilder:
flight=flight,
patrol_duration=self.doctrine.cas_duration,
takeoff=builder.takeoff(flight.from_cp),
ascent=builder.ascent(flight.from_cp),
patrol_start=builder.ingress_cas(ingress, location),
target=builder.cas(center),
patrol_end=builder.egress(egress, location),
descent=descent,
land=land
)
@ -871,12 +848,50 @@ class FlightPlanBuilder:
return builder.strike_area(location)
def _hold_point(self, flight: Flight) -> Point:
heading = flight.from_cp.position.heading_between_point(
self.package.target.position
)
return flight.from_cp.position.point_from_heading(
heading, nm_to_meter(15)
assert self.package.waypoints is not None
origin = flight.from_cp.position
target = self.package.target.position
join = self.package.waypoints.join
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.
def generate_ascend_point(self, flight: Flight,
@ -944,23 +959,37 @@ class FlightPlanBuilder:
package=self.package,
flight=flight,
takeoff=builder.takeoff(flight.from_cp),
ascent=builder.ascent(flight.from_cp),
hold=builder.hold(self._hold_point(flight)),
join=builder.join(self.package.waypoints.join),
ingress=ingress,
targets=target_waypoints,
egress=builder.egress(self.package.waypoints.egress, location),
split=builder.split(self.package.waypoints.split),
descent=descent,
land=land
)
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)
return ingress_point.point_from_heading(heading,
-self.doctrine.join_distance)
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)
return egress_point.point_from_heading(heading,
-self.doctrine.split_distance)
@ -983,6 +1012,9 @@ class FlightPlanBuilder:
def _heading_to_package_airfield(self, point: Point) -> int:
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:
# We'll always have a package, but if this is being planned via the UI
# it could be the first flight in the package.