mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Refactor strike formation timing calculations.
This commit is contained in:
parent
85491dca20
commit
ed05f995b5
@ -7,7 +7,6 @@ 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
|
||||||
@ -52,77 +51,6 @@ class InvalidObjectiveLocation(PlanningError):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class PackageWaypointTiming:
|
|
||||||
#: The package being scheduled.
|
|
||||||
package: Package
|
|
||||||
|
|
||||||
#: The package join time.
|
|
||||||
join: timedelta
|
|
||||||
|
|
||||||
#: The ingress waypoint TOT.
|
|
||||||
ingress: timedelta
|
|
||||||
|
|
||||||
#: The egress waypoint TOT.
|
|
||||||
egress: timedelta
|
|
||||||
|
|
||||||
#: The package split time.
|
|
||||||
split: timedelta
|
|
||||||
|
|
||||||
@property
|
|
||||||
def target(self) -> timedelta:
|
|
||||||
"""The package time over target."""
|
|
||||||
return self.package.time_over_target
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def for_package(cls, package: Package) -> Optional[PackageWaypointTiming]:
|
|
||||||
"""Computes and returns the timings for package formation waypoints.
|
|
||||||
|
|
||||||
Package waypoint timing depends on the composition of the package.
|
|
||||||
Whenever flights are added or removed they must be recomputed since the
|
|
||||||
mission speed may have changed.
|
|
||||||
|
|
||||||
If the package contains no flights with formation flight plans, this
|
|
||||||
returns None.
|
|
||||||
"""
|
|
||||||
assert package.waypoints is not None
|
|
||||||
|
|
||||||
if not package.flights:
|
|
||||||
raise ValueError("Cannot plan TOT for package with no flights")
|
|
||||||
|
|
||||||
group_ground_speed = package.formation_speed
|
|
||||||
if group_ground_speed is None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Round each waypoint TOT since DCS doesn't support sub-second timing
|
|
||||||
# and it's not interesting to the user.
|
|
||||||
ingress = timedelta(seconds=math.floor(
|
|
||||||
(package.time_over_target - TravelTime.between_points(
|
|
||||||
package.waypoints.ingress,
|
|
||||||
package.target.position,
|
|
||||||
group_ground_speed)).total_seconds()))
|
|
||||||
|
|
||||||
join = timedelta(seconds=math.floor(
|
|
||||||
(ingress - TravelTime.between_points(
|
|
||||||
package.waypoints.join,
|
|
||||||
package.waypoints.ingress,
|
|
||||||
group_ground_speed)).total_seconds()))
|
|
||||||
|
|
||||||
egress = timedelta(seconds=math.floor(
|
|
||||||
(package.time_over_target + TravelTime.between_points(
|
|
||||||
package.target.position,
|
|
||||||
package.waypoints.egress,
|
|
||||||
group_ground_speed)).total_seconds()))
|
|
||||||
|
|
||||||
split = timedelta(seconds=math.floor(
|
|
||||||
(egress + TravelTime.between_points(
|
|
||||||
package.waypoints.egress,
|
|
||||||
package.waypoints.split,
|
|
||||||
group_ground_speed)).total_seconds()))
|
|
||||||
|
|
||||||
return cls(package, join, ingress, egress, split)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class FlightPlan:
|
class FlightPlan:
|
||||||
package: Package
|
package: Package
|
||||||
@ -179,9 +107,8 @@ class FlightPlan:
|
|||||||
self, destination: FlightWaypoint) -> timedelta:
|
self, destination: FlightWaypoint) -> timedelta:
|
||||||
total = timedelta()
|
total = timedelta()
|
||||||
for previous_waypoint, waypoint in self.edges:
|
for previous_waypoint, waypoint in self.edges:
|
||||||
total += TravelTime.between_points(
|
total += self.travel_time_between_waypoints(previous_waypoint,
|
||||||
previous_waypoint.position, waypoint.position,
|
waypoint)
|
||||||
self.speed_between_waypoints(previous_waypoint, waypoint))
|
|
||||||
if waypoint == destination:
|
if waypoint == destination:
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
@ -190,6 +117,11 @@ class FlightPlan:
|
|||||||
f"waypoints for {self.flight}")
|
f"waypoints for {self.flight}")
|
||||||
return total
|
return total
|
||||||
|
|
||||||
|
def travel_time_between_waypoints(self, a: FlightWaypoint,
|
||||||
|
b: FlightWaypoint) -> timedelta:
|
||||||
|
return TravelTime.between_points(a.position, b.position,
|
||||||
|
self.speed_between_waypoints(a, b))
|
||||||
|
|
||||||
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@ -210,14 +142,6 @@ class FormationFlightPlan(FlightPlan):
|
|||||||
join: FlightWaypoint
|
join: FlightWaypoint
|
||||||
split: FlightWaypoint
|
split: FlightWaypoint
|
||||||
|
|
||||||
@property
|
|
||||||
def package_timing(self) -> PackageWaypointTiming:
|
|
||||||
timing = PackageWaypointTiming.for_package(self.package)
|
|
||||||
# Should be able to create a PackageWaypointTiming for any package with
|
|
||||||
# a FormationFlightPlan flight.
|
|
||||||
assert timing is not None
|
|
||||||
return timing
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def waypoints(self) -> List[FlightWaypoint]:
|
def waypoints(self) -> List[FlightWaypoint]:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
@ -266,23 +190,19 @@ class FormationFlightPlan(FlightPlan):
|
|||||||
"""The estimated time between the first waypoint and the join point."""
|
"""The estimated time between the first waypoint and the join point."""
|
||||||
return self._travel_time_to_waypoint(self.join)
|
return self._travel_time_to_waypoint(self.join)
|
||||||
|
|
||||||
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
@property
|
||||||
target_types = (
|
def join_time(self) -> timedelta:
|
||||||
FlightWaypointType.TARGET_GROUP_LOC,
|
raise NotImplementedError
|
||||||
FlightWaypointType.TARGET_POINT,
|
|
||||||
FlightWaypointType.TARGET_SHIP,
|
|
||||||
)
|
|
||||||
|
|
||||||
if waypoint.waypoint_type == FlightWaypointType.JOIN:
|
@property
|
||||||
return self.package_timing.join
|
def split_time(self) -> timedelta:
|
||||||
elif waypoint.waypoint_type in INGRESS_TYPES:
|
raise NotImplementedError
|
||||||
return self.package_timing.ingress
|
|
||||||
elif waypoint.waypoint_type in target_types:
|
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
||||||
return self.package_timing.target
|
if waypoint == self.join:
|
||||||
elif waypoint.waypoint_type == FlightWaypointType.EGRESS:
|
return self.join_time
|
||||||
return self.package_timing.egress
|
elif waypoint == self.split:
|
||||||
elif waypoint.waypoint_type == FlightWaypointType.SPLIT:
|
return self.split_time
|
||||||
return self.package_timing.split
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def depart_time_for_waypoint(
|
def depart_time_for_waypoint(
|
||||||
@ -293,7 +213,7 @@ class FormationFlightPlan(FlightPlan):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def push_time(self) -> timedelta:
|
def push_time(self) -> timedelta:
|
||||||
return self.package_timing.join - TravelTime.between_points(
|
return self.join_time - TravelTime.between_points(
|
||||||
self.hold.position,
|
self.hold.position,
|
||||||
self.join.position,
|
self.join.position,
|
||||||
GroundSpeed.for_flight(self.flight, self.hold.alt)
|
GroundSpeed.for_flight(self.flight, self.hold.alt)
|
||||||
@ -472,6 +392,43 @@ class StrikeFlightPlan(FormationFlightPlan):
|
|||||||
def mission_speed(self) -> int:
|
def mission_speed(self) -> int:
|
||||||
return GroundSpeed.for_flight(self.flight, self.ingress.alt)
|
return GroundSpeed.for_flight(self.flight, self.ingress.alt)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def join_time(self) -> timedelta:
|
||||||
|
ingress_time = self.tot_for_waypoint(self.ingress)
|
||||||
|
travel_time = self.travel_time_between_waypoints(
|
||||||
|
self.join, self.ingress)
|
||||||
|
return ingress_time - travel_time
|
||||||
|
|
||||||
|
@property
|
||||||
|
def split_time(self) -> timedelta:
|
||||||
|
egress_time = self.tot_for_waypoint(self.egress)
|
||||||
|
travel_time = self.travel_time_between_waypoints(
|
||||||
|
self.egress, self.split)
|
||||||
|
return egress_time + travel_time
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ingress_time(self) -> timedelta:
|
||||||
|
tot = self.package.time_over_target
|
||||||
|
travel_time = self.travel_time_between_waypoints(
|
||||||
|
self.ingress, self.tot_waypoint)
|
||||||
|
return tot - travel_time
|
||||||
|
|
||||||
|
@property
|
||||||
|
def egress_time(self) -> timedelta:
|
||||||
|
tot = self.package.time_over_target
|
||||||
|
travel_time = self.travel_time_between_waypoints(
|
||||||
|
self.tot_waypoint, self.egress)
|
||||||
|
return tot + travel_time
|
||||||
|
|
||||||
|
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
||||||
|
if waypoint == self.ingress:
|
||||||
|
return self.ingress_time
|
||||||
|
elif waypoint == self.egress:
|
||||||
|
return self.egress_time
|
||||||
|
elif waypoint in self.targets:
|
||||||
|
return self.package.time_over_target
|
||||||
|
return super().tot_for_waypoint(waypoint)
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class CustomFlightPlan(FlightPlan):
|
class CustomFlightPlan(FlightPlan):
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user