From 75498fe061a49a748ddb424658b8ec9c69c3c995 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sun, 13 Aug 2023 11:16:14 -0700 Subject: [PATCH] Differentiate total time and travel time. There's an ugly special case in flight simulation to handle hold points because we don't differentiate between the total time between two waypoints (which can include delays from actions like holding) and travel time. Split those up and remove the special case. --- game/ato/flightplans/flightplan.py | 19 ++++++++++++-- game/ato/flightplans/formationattack.py | 32 +++--------------------- game/ato/flightplans/loiter.py | 4 +-- game/ato/flightplans/packagerefueling.py | 4 +-- game/ato/flightplans/sweep.py | 2 +- game/ato/flightstate/inflight.py | 10 +------- 6 files changed, 27 insertions(+), 44 deletions(-) diff --git a/game/ato/flightplans/flightplan.py b/game/ato/flightplans/flightplan.py index e0419353..5e631c3f 100644 --- a/game/ato/flightplans/flightplan.py +++ b/game/ato/flightplans/flightplan.py @@ -231,8 +231,23 @@ class FlightPlan(ABC, Generic[LayoutT]): ) for previous_waypoint, waypoint in self.edges(until=destination): - total += self.travel_time_between_waypoints(previous_waypoint, waypoint) - return total + total += self.total_time_between_waypoints(previous_waypoint, waypoint) + # Trim microseconds. Our simulation tick rate is 1 second, so anything that + # takes 100.1 or 100.9 seconds will take 100 seconds. DCS doesn't handle + # sub-second resolution for tasks anyway, nor are they interesting from a + # mission planning perspective, so there's little value to keeping them in the + # model. + return timedelta(seconds=math.floor(total.total_seconds())) + + def total_time_between_waypoints( + self, a: FlightWaypoint, b: FlightWaypoint + ) -> timedelta: + """Returns the total time spent between a and b. + + The total time between waypoints differs from the travel time in that it may + include additional time for actions such as loitering. + """ + return self.travel_time_between_waypoints(a, b) def travel_time_between_waypoints( self, a: FlightWaypoint, b: FlightWaypoint diff --git a/game/ato/flightplans/formationattack.py b/game/ato/flightplans/formationattack.py index 6d4c871d..1be5669d 100644 --- a/game/ato/flightplans/formationattack.py +++ b/game/ato/flightplans/formationattack.py @@ -14,7 +14,6 @@ from game.utils import Speed, meters, nautical_miles, feet from .flightplan import FlightPlan from .formation import FormationFlightPlan, FormationLayout from .ibuilder import IBuilder -from .planningerror import PlanningError from .waypointbuilder import StrikeTarget, WaypointBuilder from .. import FlightType from ..flightwaypoint import FlightWaypoint @@ -56,42 +55,19 @@ class FormationAttackFlightPlan(FormationFlightPlan, ABC): "RADIO", ) - @property - def travel_time_to_target(self) -> timedelta: - """The estimated time between the first waypoint and the target.""" - destination = self.tot_waypoint - total = timedelta() - for previous_waypoint, waypoint in self.edges(): - if waypoint == self.tot_waypoint: - # For anything strike-like the TOT waypoint is the *flight's* - # mission target, but to synchronize with the rest of the - # package we need to use the travel time to the same position as - # the others. - total += self.travel_time_between_waypoints( - previous_waypoint, self.target_area_waypoint - ) - break - total += self.travel_time_between_waypoints(previous_waypoint, waypoint) - else: - raise PlanningError( - f"Did not find destination waypoint {destination} in " - f"waypoints for {self.flight}" - ) - return total - @property def join_time(self) -> timedelta: - travel_time = self.travel_time_between_waypoints( + travel_time = self.total_time_between_waypoints( self.layout.join, self.layout.ingress ) return self.ingress_time - travel_time @property def split_time(self) -> timedelta: - travel_time_ingress = self.travel_time_between_waypoints( + travel_time_ingress = self.total_time_between_waypoints( self.layout.ingress, self.target_area_waypoint ) - travel_time_egress = self.travel_time_between_waypoints( + travel_time_egress = self.total_time_between_waypoints( self.target_area_waypoint, self.layout.split ) minutes_at_target = 0.75 * len(self.layout.targets) @@ -106,7 +82,7 @@ class FormationAttackFlightPlan(FormationFlightPlan, ABC): @property def ingress_time(self) -> timedelta: tot = self.tot - travel_time = self.travel_time_between_waypoints( + travel_time = self.total_time_between_waypoints( self.layout.ingress, self.target_area_waypoint ) return tot - travel_time diff --git a/game/ato/flightplans/loiter.py b/game/ato/flightplans/loiter.py index ee306907..803fa729 100644 --- a/game/ato/flightplans/loiter.py +++ b/game/ato/flightplans/loiter.py @@ -33,10 +33,10 @@ class LoiterFlightPlan(StandardFlightPlan[Any], ABC): return self.push_time + self.tot_offset return None - def travel_time_between_waypoints( + def total_time_between_waypoints( self, a: FlightWaypoint, b: FlightWaypoint ) -> timedelta: - travel_time = super().travel_time_between_waypoints(a, b) + travel_time = super().total_time_between_waypoints(a, b) if a != self.layout.hold: return travel_time return travel_time + self.hold_duration diff --git a/game/ato/flightplans/packagerefueling.py b/game/ato/flightplans/packagerefueling.py index 68e7269e..dea4afbe 100644 --- a/game/ato/flightplans/packagerefueling.py +++ b/game/ato/flightplans/packagerefueling.py @@ -59,10 +59,10 @@ class PackageRefuelingFlightPlan(RefuelingFlightPlan): "REFUEL", FlightWaypointType.REFUEL, refuel, altitude ) - delay_target_to_split: timedelta = self.travel_time_between_waypoints( + delay_target_to_split: timedelta = self.total_time_between_waypoints( self.target_area_waypoint(), split_waypoint ) - delay_split_to_refuel: timedelta = self.travel_time_between_waypoints( + delay_split_to_refuel: timedelta = self.total_time_between_waypoints( split_waypoint, refuel_waypoint ) diff --git a/game/ato/flightplans/sweep.py b/game/ato/flightplans/sweep.py index 68e7e0b7..e332dbc0 100644 --- a/game/ato/flightplans/sweep.py +++ b/game/ato/flightplans/sweep.py @@ -56,7 +56,7 @@ class SweepFlightPlan(LoiterFlightPlan): @property def sweep_start_time(self) -> timedelta: - travel_time = self.travel_time_between_waypoints( + travel_time = self.total_time_between_waypoints( self.layout.sweep_start, self.layout.sweep_end ) return self.sweep_end_time - travel_time diff --git a/game/ato/flightstate/inflight.py b/game/ato/flightstate/inflight.py index c9af70c5..53e4f852 100644 --- a/game/ato/flightstate/inflight.py +++ b/game/ato/flightstate/inflight.py @@ -51,17 +51,9 @@ class InFlight(FlightState, ABC): return index <= self.waypoint_index def travel_time_between_waypoints(self) -> timedelta: - travel_time = self.flight.flight_plan.travel_time_between_waypoints( + return self.flight.flight_plan.travel_time_between_waypoints( self.current_waypoint, self.next_waypoint ) - if self.current_waypoint.waypoint_type is FlightWaypointType.LOITER: - # Loiter time is already built into travel_time_between_waypoints. If we're - # at a loiter point but still a regular InFlight (Loiter overrides this - # method) that means we're traveling from the loiter point but no longer - # loitering. - assert self.flight.flight_plan.is_loiter(self.flight.flight_plan) - travel_time -= self.flight.flight_plan.hold_duration - return travel_time @abstractmethod def estimate_position(self) -> Point: